Planteo del problema
En este caso vamos a tratar de resolver el problema de predecir si una persona que viajaba a bordo del Titanic sobrevivió o no. En particular, la regresión logística es útil para problemas de predicción de clases.
Queremos estimar \(P(Survived=1|X)=P(X)\) para cada individuo y a partir de ello poder definir un punto de corte para predecir quiénes son los que van a sobrevivir y los que no.
Para ello, utilizaremos el conjunto de datos que proviene de Kaggle - Titanic: Machine Learning from Disaster.
# Cargamos las librerías que vamos a utilizar
library(tidyverse)
library(tidymodels)
library(modelr)
library(GGally)
library(pROC)
library(cowplot)
library(OneR)
library(rlang)
library(caret)
set.seed(2021)
# cargamos los datasets de train y test
dftitanic_train <- read_csv("../Fuentes/titanic_complete_train.csv")
dftitanic_test <- read_csv("../Fuentes/titanic_complete_test.csv")
# observamos su estructura
glimpse(dftitanic_train)
Rows: 891
Columns: 12
$ PassengerId <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,~
$ Survived <dbl> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,~
$ Pclass <dbl> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, 3, 2,~
$ Name <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bradley (Floren~
$ Sex <chr> "male", "female", "female", "female", "male", "male", "male", ~
$ Age <dbl> 22.00000, 38.00000, 26.00000, 35.00000, 35.00000, 26.50759, 54~
$ SibSp <dbl> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0,~
$ Parch <dbl> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0, 0,~
$ Ticket <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "113803", "373450~
$ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21.~
$ Cabin <chr> NA, "C85", NA, "C123", NA, NA, "E46", NA, NA, NA, "G6", "C103"~
$ Embarked <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", "S", "S~
glimpse(dftitanic_test)
Rows: 418
Columns: 12
$ PassengerId <dbl> 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 90~
$ Pclass <dbl> 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 1, 1, 2, 1, 2, 2, 3, 3, 3, 1,~
$ Name <chr> "Kelly, Mr. James", "Wilkes, Mrs. James (Ellen Needs)", "Myles~
$ Sex <chr> "male", "female", "male", "male", "female", "male", "female", ~
$ Age <dbl> 34.50000, 47.00000, 62.00000, 27.00000, 22.00000, 14.00000, 30~
$ SibSp <dbl> 0, 1, 0, 0, 1, 0, 0, 1, 0, 2, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1,~
$ Parch <dbl> 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
$ Ticket <chr> "330911", "363272", "240276", "315154", "3101298", "7538", "33~
$ Fare <dbl> 7.8292, 7.0000, 9.6875, 8.6625, 12.2875, 9.2250, 7.6292, 29.00~
$ Cabin <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "B45", NA, "E3~
$ Embarked <chr> "Q", "S", "Q", "S", "S", "S", "Q", "S", "C", "S", "S", "S", "S~
$ Survived <dbl> 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0,~
El dataset de training tiene 12 variables y 891 observaciones, mientras que el de testing tiene 418.
Las variables del set incluyen id, nombre, edad, sexo del pasajero, como también:
- Survived: La clase que queremos predecir: Sobrevivió (0 = No, 1 = Yes).
- Pclass: La clase a la que pertenece el pasajero (1 = 1st, 2 = 2nd, 3 = 3rd). Es un proxy del estatus socio-económico (1st = Upper, 2nd = Middle, 3rd = Lower).
- Sibsp: número de hermanos / cónyuges a bordo del Titanic.
- Parch: número de padres / hijos a bordo del Titanic.
- Ticket: número de Ticket
- Fare: tarifa.
- Cabin: número de cabina
- Embarked: Puerto de embarque (C = Cherbourg, Q = Queenstown, S = Southampton).
Se transforman las variables Pclass y Embarked a factor.
dftitanic_train <- dftitanic_train %>%
mutate(Pclass = factor(Pclass), Embarked = factor(Embarked))
dftitanic_test <- dftitanic_test %>%
mutate(Pclass = factor(Pclass), Embarked = factor(Embarked))
Análisis Exploratorios
Analicemos la distribución de la clase en cada dataset.
# calculamos la distribución de clase en cada dataset
train <- dftitanic_train %>%
group_by(Survived) %>%
summarise(numero_casos=n()) %>%
mutate(prop = round(prop.table(numero_casos)*100,2))
test <- dftitanic_test %>%
group_by(Survived) %>%
summarise(numero_casos=n()) %>%
mutate(prop = round(prop.table(numero_casos)*100,2))
# armamos tabla conjunta para graficar
distrib = cbind(rbind(train, test), dataset = c("train", "train", "test", "test"))
distrib
# grafico las distribuciones
ggplot(distrib, aes(x = Survived, y = prop, fill = factor(Survived), label = prop)) +
geom_bar(stat="identity", position = "dodge") + facet_wrap(~ dataset) +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
labs(x = "Sobrevivientes", y = "Proporción en %", title = "Proporción de sobrevivientes por dataset") +
theme_bw() +
scale_fill_brewer(palette="Set1")

Vemos que estamos trabajando con un problema de clasificación con cierto desbalance de clase, pero que la proporción se mantiene en ambos conjuntos de datos.
Realizamos un gráfico exploratorio completo para ver el comportamiento y las relaciones entre las variables. El color rojo designa a quienes no sobrevivieron y el azul a los que sí.
# graficamos con ggpairs coloreando por variable a predecir
g <- dftitanic_train %>%
select("Survived","Pclass", "Sex", "Age", "Fare", "SibSp", "Parch") %>%
ggpairs(title = "Correlograma de variables",
mapping = aes(colour= factor(Survived)),
progress = FALSE,
lower=list(combo=wrap("facethist", binwidth=0.8))) +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
theme_bw() +
scale_fill_brewer(palette="Set1") +
scale_color_brewer(palette="Set1")
g

¿Qué pueden decir de la relación entre clase (Pclass) y supervivencia (Survived)?
¿Y entre edad (Age) y supervivencia?
¿Cuáles parecen ser buenas variables para discriminar entre quienes sobrevivieron y quienes no?
Probemos algunas soluciones
Regresión lineal
En este caso estamos modelando la probabilidad de la siguiente manera:
\(P(X)= \beta_0 + \sum\limits_{j=1}^p \beta_j X\)
Veamos que tan bueno es el modelo lineal para esto, usando la edad como predictor.
mrl <- dftitanic_train %>%
lm(formula = Survived ~ Age)
tdy = mrl %>% tidy()
tdy
mrl %>% glance()
Los estimadores son significativos y el test de significatividad global del modelo también es significativo.
Veamos un gráfico de nuestro modelo.

Parece tener bastantes problemas para estimar la probabilidad de supervivencia de los individuo: no existe un punto de corte claro, la predicción podría ser mayor a 1 o menor a cero llegado el caso.
Regresión Logística
Para evitar estos problemas, usamos la función logística.
\(P(Y=1|X)= \frac{e^{\beta_0 + \sum\limits_{j=1}^p \beta_j X}}{1+e^{\beta_0 + \sum\limits_{j=1}^p \beta_j X}}\)
El lado derecho se llama expit
Esta función acota el resultado entre 0 y 1, lo cual es mucho más adecuado para modelar una probabilidad.
Luego de hacer algunas operaciones, podemos llegar a la expresión:
\(\log {\frac{P(x)}{1-P(x)}}= \beta_0 + \sum\limits_{j=1}^p \beta_j X\)
El lado izquierdo es el logaritmo de los odds y se llama logit.
Modelo
La funcíón glm() nos permite crear un modelo lineal generalizado (Generalized Linear Model). Al igual que la función lm() toma como argumentos una formula y los datos pero también se debe especificar el argumento family: indicamos la distribución del error y la función link que vamos a utilizar en el modelo.
Algunas familias son:
Como estamos trabajando con un fenómeno que suponemos tiene una distribución binomial, así lo especificamos en el parámetro family.
Realizamos un modelo de regresión logística para predecir la supervivencia en función de Pclass, Sex y Age.
# modelo de regresión logística
glm1 <- glm(data = dftitanic_train, Survived ~ Pclass + Sex + Age, family = 'binomial')
# veo los resultados
tidy(glm1)
glance(glm1)
Se va a discutir sobre la interpretación de coeficientes y la evaluación en las siguientes secciones.
Creación de fórmulas
Para crear varios modoelos de regresión logística podemos utilizar la función formulas del paquete modelr para crear un objeto que contiene todas las fórmulas que vamos a utilizar.
En .response especificamos la variable respuesta de nuestras fórmulas y luego nombramos las fórmulas que queramos armar.
Así, armaremos distintos modelos combinando distintas variables. Se generan 7 modelos distintos para predecir la supervivencia en función de distintas combinaciones de las variables pclass, sex, age y fare.
# Creación de fórmulas
logit_formulas <- formulas(.response = ~ Survived,
class = ~ Pclass,
sex = ~ Sex,
age = ~ Age,
PcS = ~ Pclass + Sex, # modelo con las variables que más parecen dividir el target
PcSA = ~ Pclass + Sex + Age, # modelo glm1
PcSF = ~ Pclass + Sex + Fare, # modelo previo sin Age
PcSAF = ~ Pclass + Sex + Age + Fare # modelo glm1 con Fare
)
logit_formulas # observamos el objeto formulas
$class
Survived ~ Pclass
$sex
Survived ~ Sex
$age
Survived ~ Age
$PcS
Survived ~ Pclass + Sex
$PcSA
Survived ~ Pclass + Sex + Age
$PcSF
Survived ~ Pclass + Sex + Fare
$PcSAF
Survived ~ Pclass + Sex + Age + Fare
Creación de modelos
Procedemos a crear los modelos a partir de estas fórmulas.
models <- data_frame(logit_formulas) %>% # dataframe a partir del objeto formulas
mutate(models = names(logit_formulas), # columna con los nombres de las formulas
expression = paste(logit_formulas), # columna con las expresiones de las formulas
mod = map(logit_formulas, ~glm(., family = 'binomial', data = dftitanic_train))) # Que estamos haciendo acá? Que vamos a encontrar en la columna?
models
Modelos simples
Probamos los primeros tres modelos, aquellos que tienen un único predictor. Usamos la función tidy para obtener los parámetros estimados para estos tres modelos.
models %>%
filter(models %in% c('class','sex','age')) %>%
mutate(tidy = map(mod, tidy)) # Qué realizamos en este paso? Que va a tener esta columna?
Para acceder a los elementos de la nueva columna tidy debemos desanidarla (usando unnest()). El anidado crea una columna de listas de dataframes, es implícitamente una operación de resumen: obtiene una fila para cada grupo definido por las columnas no anidadas. Desanidar lo aplana de nuevo en columnas regulares. Para mayor detalle, sugerimos ver la documentación de tidyr sobre Nest and unnest.
models %>%
filter(models %in% c('class','sex','age')) %>%
mutate(tidy = map(mod, tidy)) %>% # Qué realizamos en este paso? Que va a tener esta columna?
unnest(tidy) %>%
mutate(estimate=round(estimate,5), # redondeamos valores para facilitar lectura
p.value=round(p.value,4))
Observamos que todos los modelos tienen coeficientes significativos aunque el de Age se encuentra muy cercano a nuestro valor de rechazo.
Interpretación de los coeficientes
Recordando la ecuación para modelar la probabilidad:
\(P(Y=1|X)= \frac{e^{\beta_0 + \sum\limits_{j=1}^p \beta_j X}}{1+e^{\beta_0 + \sum\limits_{j=1}^p \beta_j X}}\)
Se observa que ahora las variables ya no tienen una relación lineal con la probabilidad. En este modelo un coeficiente positivo indica que frente a aumentos de dicha variable la probabilidad aumenta, mientras que un coeficiente negativo nos indica lo contrario. Para nuestros modelos:
Modelo class
β0 = 0.53063 corresponde a personas que viajaron en primera clase.
β1 = -0.63943 y β2 = -1.6704 corresponden a personas que viajaron en la segunda y tercera clase, respectivamente. El coeficiente estimado en ambos casos fue negativo, indicando que la probabilidad de supervivencia disminuye en comparación con la primera clase. Al ser más negativo el coeficiente estimado de Pclass3, se espera que la probabilidad de Supervivencia sea menor aún para esta clase que para un pasajero en clase 2.
Modelo sex
β0 = 1.05659 corresponde a mujeres que viajaron a bordo del titanic.
β1 = -2.51371 representa a los hombres que viajaron en el titanic e indica que la probabilidad de supervivencia se reduce en comparación a las mujeres.
Modelo age
- β1 = -0.0106 indica que la probabilidad de supervivencia disminuye por cada año más de edad de la persona.
¿Qué pasaría en un modelo múltiple?
models %>%
filter(models %in% c('PcSA')) %>%
mutate(tidy = map(mod, tidy)) %>%
unnest(tidy) %>%
mutate(estimate=round(estimate,5),
p.value=round(p.value,4))
Así como se observó en el modelo simple para la variable Pclass, en este caso los coeficientes estimados de Pclass2 y Pclass3 resultan negativos, indicando que si el pasajero pertenece a segunda o tercera clase, su probabilidad de superviviencia esperada disminuye respecto de una persona de primera clase, dadas las demás variables del modelo (Sex y Age).
Para la variable Sex observamos que el coeficiente SexMale resulta negativo; esto significa que la probabilidad de supervivencia esperada de los hombres disminuye respecto a la de las mujeres, dadas las demás variables del modelo.
Para la variable Age, el coeficiente estimado también resulta negativo, indicando que ante un aumento en la edad del pasajero, su probabilidad de supervivencia esperada disminuye, dadas las demás variables.
Asimismo, se observa que los coeficientes resultan todos significativos en este modelo (p-valor<0.05).
Recomendamos leer el capítulo de regresión logística de Interpretable Machine Learning: A Guide for Making Black Box Models Explainable de Molnar Cristoph para una discusión más profunda de la interpretación de un modelo de regresión logística.
Evaluación de todos los modelos
Con map() agregamos la función glance para traer información relevante para la evaluación del modelo. Con unnest() accedemos a dicha información. Por último, agregamos una columna con el porcentaje de deviance explicado por cada modelo y ordenamos el dataset según su valor de deviance.
# Calcular las medidas de evaluación para cada modelo
models <- models %>%
mutate(glance = map(mod,glance))
# Obtener las medidas de evaluacion de interes
models %>%
unnest(glance) %>%
# Calculamos la deviance explicada
mutate(perc_explained_dev = 1-deviance/null.deviance) %>%
select(-c(models, df.null, AIC, BIC)) %>%
arrange(deviance)
Los modelos que incluyen las 3 variables Pclass, Sex y Age parecen ser los que minimizan la deviance. Observamos también que los 2 últimos modelos reducen muy poco la deviance respecto a la deviance nula.
Gráficos de Evaluación
Realizamos los gráficos para el modelo completo y uno de los modelos con mayor deviance (Age).
Comenzamos agregando las predicciones con augment con el parámetro type="response". La función augment hereda el argumento type.predict de la función predict.
Si type.predict = 'link' la predicción es en términos de la función link. En nuestro caso son el logaritmo de las odds, es decir, los valores que toma la expresión logit.
Si type.predict = 'response' la predicción son las probabilidades de que la observación pertenezca a la clase positiva. En nuestro caso, devuelve la probabilidad de la que persona sobreviva.
models$pred$PcSAF %>% arrange(.fitted) %>% head(10)
#Observaciones con probabilidad más alta
models$pred$PcSAF %>% arrange(desc(.fitted)) %>% head(10)
Guardamos las predicciones para los modelos mencionados.
# Modelo completo
prediction_full <- models %>%
filter(models=="PcSAF") %>%
unnest(pred)
#Modelo malo
prediction_bad <- models %>%
filter(models=="age") %>%
unnest(pred)
Violin plots
# graficamos el modelo completo
violin_full = ggplot(prediction_full, aes(x=Survived, y=.fitted, group=Survived, fill=factor(Survived))) +
geom_violin() +
theme_bw() +
guides(scale="none") +
labs(title='Violin plot', subtitle='Modelo completo', y='Predicted probability')
# graficamos el modelo malo
violin_bad = ggplot(prediction_bad, aes(x=Survived, y=.fitted, group=Survived, fill=factor(Survived))) +
geom_violin() +
theme_bw() +
guides(scale="none") +
labs(title='Violin plot', subtitle='Modelo malo', y='Predicted probability')
# mostramos ambos
plot_grid(violin_bad, violin_full)

En los gráficos de violin observamos:
En el eje de abscisas la clase verdadera: Survived o No Survived (1 o 0).
En el eje de ordenadas la probabilidad predicha por nuestro modelo.
El gráfico nos muestra la distribución de la cantidad de observaciones por su clase real y la probabilidad que le asigna nuestro modelo.
¿Cuál parece ser un punto de corte adecuado para cada modelo?
Gráfico de Hosmer-Lemeshow
Se genera una función para realizar un gráfico de Hosmer-Lemeshow para un dataset. Para ello se fijan los siguientes parámetros:
dataset: conjunto de datos
predicted_column: columna con la probabilidad predicha
class_column: columna con la clase a predecir
possitive_value: valor de la clase a predecir
bins: cantidad de grupos del gráfico
color: color de los puntos
nudge_x: desplazamiento de la etiqueta en el eje x
nudge_y: desplazamiento de la etiqueta en el eje y
Hosmer_Lemeshow_plot <- function(dataset, predicted_column, class_column, bins, positive_value, color='forestgreen', nudge_x=0, nudge_y=0.05){
# Asignar los grupos a las observaciones de acuerdo a la probabilidad predicha
dataset['group'] <- bin(dataset[predicted_column], nbins = bins, method = 'l', labels=c(1:bins))
# Contar la cantidad de casos positivos por grupo
positive_class <- dataset %>% filter(!!sym(class_column)==positive_value) %>% group_by(group) %>% count()
# Obtener la media de las predicciones por grupo
HL_df <- dataset %>% group_by(group) %>% summarise(pred=mean(!!sym(predicted_column)), count=n()) %>%
inner_join(.,positive_class) %>%
mutate(freq=n/count)
# Gráfico
HM_plot <- ggplot(HL_df, aes(x=pred, y=freq)) +
geom_point(aes(size=n), color=color) +
geom_text(aes(label=n),nudge_y = nudge_y)+
geom_abline(slope = 1, intercept = 0, linetype='dashed') +
theme_bw() +
labs(title='Hosmer-Lemeshow', size='Casos', x="Probabilidad Predicha", y="Frecuencia observada")
return(HM_plot)
}
Generamos los gráficos pasandole lo parámetros.
# modelo completo
Hosmer_Lemeshow_plot(prediction_full, '.fitted', 'Survived', 10, 1) +
labs(subtitle="Modelo completo")
Joining, by = "group"

# modelo malo
Hosmer_Lemeshow_plot(prediction_bad, '.fitted', 'Survived', 10, 1, color = "firebrick") + labs(subtitle="Modelo malo")
Joining, by = "group"

En los gráficos de Hosmer-Lemeshow observamos:
En el eje de abscisas la probabilidad predicha de supervivencia.
En el eje de ordenadas la frecuencia de clase, el cociente entre cantidad de individuos Survived y el total de individuos.
La línea punteada designa la igualdad entre probabilidad predicha y frecuencia de clase.
Los círculos, que se construyen de la siguiente manera:
- Se dividen a las observaciones en bins en base a la probabilidad predicha
- Se calcula la frecuencia de clase para cada bin
- En base a estas dos coordenadas se ubica al círculo en el gráfico
- El número y tamaño indican la cantidad de observaciones en dicho grupo
Aquellos círculos que se ubiquen por encima de la línea punteada indican que el modelo está subestimando la probabilidad para dichos grupos. Mientras que si los círculos se ubican por debajo el modelo está sobreestimando la probabilidad para dichos grupos.
¿Para qué valores parece existir una sobreestimación de la probabilidad? ¿Para cuáles subestimación?
Curvas ROC
# Calculamos curvas ROC
roc_full <- roc(response=prediction_full$Survived, predictor=prediction_full$.fitted)
roc_bad <- roc(response=prediction_bad$Survived, predictor=prediction_bad$.fitted)
Graficamos ambas en un mismo plot.
ggroc(list(full=roc_full, bad=roc_bad), size=1) +
geom_abline(slope = 1, intercept = 1, linetype='dashed') +
theme_bw() +
labs(title='Curvas ROC', color='Modelo')

print(paste('AUC: Modelo completo', round(roc_full$auc,3)))
[1] "AUC: Modelo completo 0.848"
print(paste('AUC: Modelo malo', round(roc_bad$auc,3)))
[1] "AUC: Modelo malo 0.474"
¿Qué significa cada uno de los ejes?
Punto de corte
Hasta ahora hemos evaluado el modelo de manera general, pero el resultado final del modelo debe consistir en asignar a la persona una clase predicha. En nuestro caso debemos establecer un punto de corte según el cual vamos a separar a las personas en quienes sobreviven y quienes no.
Probamos varios puntos de corte y graficamos el accuracy, la sensibilidad o recall, la especificidad y la precisión para cada uno de ellos.
| Negativa |
True Neg |
False Neg |
| Positiva |
False Pos |
True Pos |
Recordemos que:
\(accuracy = \frac{TP+TN}{TP+FP+FN+TN}\)
\(sensitivity = recall = \frac{TP}{TP+FN}\)
\(specificity = \frac{TN}{TN+FP}\)
\(precision = \frac{TP}{TP+FP}\)
prediction_metrics <- function(cutoff, predictions=prediction_full){
tab <- predictions %>%
mutate(predicted_class = if_else(.fitted > cutoff, 1, 0),
Survived = factor(Survived))
confusionMatrix(table(tab$predicted_class, tab$Survived), positive = "1") %>%
tidy() %>%
select(term, estimate) %>%
filter(term %in% c('accuracy', 'sensitivity', 'specificity', 'precision')) %>%
mutate(cutoff = cutoff)
}
cutoffs = seq(0.05,0.95,0.01)
logit_pred = map_df(cutoffs, prediction_metrics) %>%
mutate(term = as.factor(term), estimate = round(estimate, 3))
ggplot(logit_pred, aes(cutoff,estimate, group=term, color=term)) + geom_line(size=1) +
theme_bw() +
labs(title= 'Accuracy, Sensitivity, Specificity y Precision', subtitle= 'Modelo completo', color="")

¿Qué podemos observar en el gráfico?
¿Podemos definir un buen punto de corte? ¿Cuál sería?
¿Por qué la especificidad tiene ese comportamiento?
Dataset de testing
Seleccionamos el modelo completo, ya que es el que maximizaba el porcentaje de deviance explicada y en base a lo que vimos definimos un punto de corte en 0.4 (pueden probar otros), donde se cruzan sensitivity y specificity.
sel_cutoff = 0.4
# Creamos el modelo
full_model <- glm(logit_formulas$PcSAF, family = 'binomial', data = dftitanic_train)
# Agregamos la predicciones al dataset de testeo
table= augment(x = full_model, newdata=dftitanic_test, type.predict='response')
# Clasificamos utilizamos el punto de corte
table=table %>%
mutate(predicted_class = if_else(.fitted>sel_cutoff, 1, 0) %>% as.factor(),
Survived = factor(Survived))
# Creamos la matriz de confusión
confusionMatrix(table(table$predicted_class, table$Survived), positive = "1")
Confusion Matrix and Statistics
0 1
0 195 42
1 66 115
Accuracy : 0.7416
95% CI : (0.6968, 0.7829)
No Information Rate : 0.6244
P-Value [Acc > NIR] : 2.496e-07
Kappa : 0.4654
Mcnemar's Test P-Value : 0.02689
Sensitivity : 0.7325
Specificity : 0.7471
Pos Pred Value : 0.6354
Neg Pred Value : 0.8228
Prevalence : 0.3756
Detection Rate : 0.2751
Detection Prevalence : 0.4330
Balanced Accuracy : 0.7398
'Positive' Class : 1
Al existir desbalanceo de clases, es posible que el analista desee saber si la precisión general (Accuracy) de su modelo es mejor que la proporción de datos con la clase mayoritaria (No-information Rate). confusionMatrix usa la función binom.test para probar que la precisión (Acc) es mejor que la tasa sin información (NIR). Si el P-Value [Acc > NIR] resulta significativo, entonces podemos decir que existe evidencia estadísticamente significativa para decir que la precisión del modelo es mejor que la tasa sin información.
LS0tDQp0aXRsZTogIlJlZ3Jlc2nDs24gTG9nw61zdGljYSINCmF1dGhvcjogIkp1YW4gQmFycmlvbGEgeSBTb2bDrWEgUGVyaW5pIg0KZGF0ZTogIjMwIGRlIE9jdHVicmUgZGUgMjAyMSINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQpkaXYubWFpbi1jb250YWluZXIgew0KICBtYXgtd2lkdGg6IDE2MDBweDsNCiAgbWFyZ2luLWxlZnQ6IGF1dG87DQogIG1hcmdpbi1yaWdodDogYXV0bzsNCn0NCjwvc3R5bGU+DQoNCiMjIFBsYW50ZW8gZGVsIHByb2JsZW1hDQoNCkVuIGVzdGUgY2FzbyB2YW1vcyBhIHRyYXRhciBkZSByZXNvbHZlciBlbCBwcm9ibGVtYSBkZSAqKnByZWRlY2lyIHNpIHVuYSBwZXJzb25hIHF1ZSB2aWFqYWJhIGEgYm9yZG8gZGVsIFRpdGFuaWMgc29icmV2aXZpw7MgbyBubyoqLiBFbiBwYXJ0aWN1bGFyLCBsYSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EgZXMgw7p0aWwgcGFyYSBwcm9ibGVtYXMgZGUgcHJlZGljY2nDs24gZGUgY2xhc2VzLg0KDQpRdWVyZW1vcyBlc3RpbWFyICRQKFN1cnZpdmVkPTF8WCk9UChYKSQgcGFyYSBjYWRhIGluZGl2aWR1byB5IGEgcGFydGlyIGRlIGVsbG8gcG9kZXIgZGVmaW5pciB1biBwdW50byBkZSBjb3J0ZSBwYXJhIHByZWRlY2lyIHF1acOpbmVzIHNvbiBsb3MgcXVlIHZhbiBhIHNvYnJldml2aXIgeSBsb3MgcXVlIG5vLg0KDQpQYXJhIGVsbG8sIHV0aWxpemFyZW1vcyBlbCBjb25qdW50byBkZSBkYXRvcyBxdWUgcHJvdmllbmUgZGUgW0thZ2dsZSAtIFRpdGFuaWM6IE1hY2hpbmUgTGVhcm5pbmcgZnJvbSBEaXNhc3Rlcl0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9jL3RpdGFuaWMvb3ZlcnZpZXcpLg0KDQpgYGB7ciwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBDYXJnYW1vcyBsYXMgbGlicmVyw61hcyBxdWUgdmFtb3MgYSB1dGlsaXphcg0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KG1vZGVscikNCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShjb3dwbG90KQ0KbGlicmFyeShPbmVSKQ0KbGlicmFyeShybGFuZykNCmxpYnJhcnkoY2FyZXQpDQpzZXQuc2VlZCgyMDIxKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KIyBjYXJnYW1vcyBsb3MgZGF0YXNldHMgZGUgdHJhaW4geSB0ZXN0IA0KZGZ0aXRhbmljX3RyYWluIDwtIHJlYWRfY3N2KCIuLi9GdWVudGVzL3RpdGFuaWNfY29tcGxldGVfdHJhaW4uY3N2IikNCmRmdGl0YW5pY190ZXN0IDwtIHJlYWRfY3N2KCIuLi9GdWVudGVzL3RpdGFuaWNfY29tcGxldGVfdGVzdC5jc3YiKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KIyBvYnNlcnZhbW9zIHN1IGVzdHJ1Y3R1cmENCmdsaW1wc2UoZGZ0aXRhbmljX3RyYWluKQ0KZ2xpbXBzZShkZnRpdGFuaWNfdGVzdCkNCmBgYA0KDQpFbCBkYXRhc2V0IGRlIHRyYWluaW5nIHRpZW5lIDEyIHZhcmlhYmxlcyB5IDg5MSBvYnNlcnZhY2lvbmVzLCBtaWVudHJhcyBxdWUgZWwgZGUgdGVzdGluZyB0aWVuZSA0MTguDQoNCkxhcyB2YXJpYWJsZXMgZGVsIHNldCBpbmNsdXllbiBpZCwgbm9tYnJlLCBlZGFkLCBzZXhvIGRlbCBwYXNhamVybywgY29tbyB0YW1iacOpbjoNCg0KKiAqKlN1cnZpdmVkKio6IExhIGNsYXNlIHF1ZSBxdWVyZW1vcyBwcmVkZWNpcjogU29icmV2aXZpw7MgKDAgPSBObywgMSA9IFllcykuDQoqICoqUGNsYXNzKio6IExhIGNsYXNlIGEgbGEgcXVlIHBlcnRlbmVjZSBlbCBwYXNhamVybyAoMSA9IDFzdCwgMiA9IDJuZCwgMyA9IDNyZCkuIEVzIHVuIHByb3h5IGRlbCBlc3RhdHVzIHNvY2lvLWVjb27Ds21pY28gKDFzdCA9IFVwcGVyLCAybmQgPSBNaWRkbGUsIDNyZCA9IExvd2VyKS4gDQoqICoqU2lic3AqKjogbsO6bWVybyBkZSBoZXJtYW5vcyAvIGPDs255dWdlcyBhIGJvcmRvIGRlbCBUaXRhbmljLg0KKiAqKlBhcmNoKio6IG7Dum1lcm8gZGUgcGFkcmVzIC8gaGlqb3MgYSBib3JkbyBkZWwgVGl0YW5pYy4JDQoqICoqVGlja2V0Kio6IG7Dum1lcm8gZGUJVGlja2V0DQoqICoqRmFyZSoqOiB0YXJpZmEuIA0KKiAqKkNhYmluKio6IG7Dum1lcm8gZGUgY2FiaW5hDQoqICoqRW1iYXJrZWQqKjogUHVlcnRvIGRlIGVtYmFycXVlIChDID0gQ2hlcmJvdXJnLCBRID0gUXVlZW5zdG93biwgUyA9IFNvdXRoYW1wdG9uKS4gDQoNClNlIHRyYW5zZm9ybWFuIGxhcyB2YXJpYWJsZXMgKipQY2xhc3MqKiB5ICoqRW1iYXJrZWQqKiBhIGZhY3Rvci4NCg0KYGBge3J9DQpkZnRpdGFuaWNfdHJhaW4gPC0gZGZ0aXRhbmljX3RyYWluICU+JQ0KICBtdXRhdGUoUGNsYXNzID0gZmFjdG9yKFBjbGFzcyksIEVtYmFya2VkID0gZmFjdG9yKEVtYmFya2VkKSkNCmRmdGl0YW5pY190ZXN0IDwtIGRmdGl0YW5pY190ZXN0ICU+JQ0KICBtdXRhdGUoUGNsYXNzID0gZmFjdG9yKFBjbGFzcyksIEVtYmFya2VkID0gZmFjdG9yKEVtYmFya2VkKSkNCmBgYA0KDQojIyBBbsOhbGlzaXMgRXhwbG9yYXRvcmlvcw0KDQpBbmFsaWNlbW9zIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbGEgY2xhc2UgZW4gY2FkYSBkYXRhc2V0LiANCg0KYGBge3J9DQojIGNhbGN1bGFtb3MgbGEgZGlzdHJpYnVjacOzbiBkZSBjbGFzZSBlbiBjYWRhIGRhdGFzZXQNCnRyYWluIDwtIGRmdGl0YW5pY190cmFpbiAlPiUgDQogIGdyb3VwX2J5KFN1cnZpdmVkKSAlPiUgDQogIHN1bW1hcmlzZShudW1lcm9fY2Fzb3M9bigpKSAlPiUNCiAgbXV0YXRlKHByb3AgPSByb3VuZChwcm9wLnRhYmxlKG51bWVyb19jYXNvcykqMTAwLDIpKQ0KdGVzdCA8LSBkZnRpdGFuaWNfdGVzdCAlPiUgDQogIGdyb3VwX2J5KFN1cnZpdmVkKSAlPiUgDQogIHN1bW1hcmlzZShudW1lcm9fY2Fzb3M9bigpKSAlPiUNCiAgbXV0YXRlKHByb3AgPSByb3VuZChwcm9wLnRhYmxlKG51bWVyb19jYXNvcykqMTAwLDIpKQ0KIyBhcm1hbW9zIHRhYmxhIGNvbmp1bnRhIHBhcmEgZ3JhZmljYXINCmRpc3RyaWIgPSBjYmluZChyYmluZCh0cmFpbiwgdGVzdCksIGRhdGFzZXQgPSBjKCJ0cmFpbiIsICJ0cmFpbiIsICJ0ZXN0IiwgInRlc3QiKSkNCmRpc3RyaWINCiMgZ3JhZmljYW1vcyBsYXMgZGlzdHJpYnVjaW9uZXMNCmdncGxvdChkaXN0cmliLCBhZXMoeCA9IFN1cnZpdmVkLCB5ID0gcHJvcCwgZmlsbCA9IGZhY3RvcihTdXJ2aXZlZCksIGxhYmVsID0gcHJvcCkpICsgDQogICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKyBmYWNldF93cmFwKH4gZGF0YXNldCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArDQogIGxhYnMoeCA9ICJTb2JyZXZpdmllbnRlcyIsIHkgPSAiUHJvcG9yY2nDs24gZW4gJSIsIHRpdGxlID0gIlByb3BvcmNpw7NuIGRlIHNvYnJldml2aWVudGVzIHBvciBkYXRhc2V0IikgKyANCiAgdGhlbWVfYncoKSArDQogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKQ0KYGBgDQoNClZlbW9zIHF1ZSBlc3RhbW9zIHRyYWJhamFuZG8gY29uIHVuIHByb2JsZW1hIGRlIGNsYXNpZmljYWNpw7NuIGNvbiBjaWVydG8gZGVzYmFsYW5jZSBkZSBjbGFzZSwgcGVybyBxdWUgbGEgcHJvcG9yY2nDs24gc2UgbWFudGllbmUgZW4gYW1ib3MgY29uanVudG9zIGRlIGRhdG9zLiANCg0KUmVhbGl6YW1vcyB1biBncsOhZmljbyBleHBsb3JhdG9yaW8gY29tcGxldG8gcGFyYSB2ZXIgZWwgY29tcG9ydGFtaWVudG8geSBsYXMgcmVsYWNpb25lcyBlbnRyZSBsYXMgdmFyaWFibGVzLiBFbCBjb2xvciByb2pvIGRlc2lnbmEgYSBxdWllbmVzIG5vIHNvYnJldml2aWVyb24geSBlbCBhenVsIGEgbG9zIHF1ZSBzw60uDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02LCBwcm9ncmVzcz1GQUxTRX0NCiMgZ3JhZmljYW1vcyBjb24gZ2dwYWlycyBjb2xvcmVhbmRvIHBvciB2YXJpYWJsZSBhIHByZWRlY2lyDQpnIDwtIGRmdGl0YW5pY190cmFpbiAlPiUgDQogICAgICAgIHNlbGVjdCgiU3Vydml2ZWQiLCJQY2xhc3MiLCAiU2V4IiwgIkFnZSIsICJGYXJlIiwgIlNpYlNwIiwgIlBhcmNoIikgJT4lIA0KICAgICAgICBnZ3BhaXJzKHRpdGxlID0gIkNvcnJlbG9ncmFtYSBkZSB2YXJpYWJsZXMiLA0KICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoY29sb3VyPSBmYWN0b3IoU3Vydml2ZWQpKSwNCiAgICAgICAgICAgICAgICBwcm9ncmVzcyA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICBsb3dlcj1saXN0KGNvbWJvPXdyYXAoImZhY2V0aGlzdCIsIGJpbndpZHRoPTAuOCkpKSArDQogICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpICsgDQogICAgICAgIHRoZW1lX2J3KCkgKw0KICAgICAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKw0KICAgICAgICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpDQpnDQpgYGANCg0Kwr9RdcOpIHB1ZWRlbiBkZWNpciBkZSBsYSByZWxhY2nDs24gZW50cmUgY2xhc2UgKFBjbGFzcykgeSBzdXBlcnZpdmVuY2lhIChTdXJ2aXZlZCk/DQoNCsK/WSBlbnRyZSBlZGFkIChBZ2UpIHkgc3VwZXJ2aXZlbmNpYT8NCg0Kwr9DdcOhbGVzIHBhcmVjZW4gc2VyIGJ1ZW5hcyB2YXJpYWJsZXMgcGFyYSBkaXNjcmltaW5hciBlbnRyZSBxdWllbmVzIHNvYnJldml2aWVyb24geSBxdWllbmVzIG5vPw0KDQojIyMgUHJvYmVtb3MgYWxndW5hcyBzb2x1Y2lvbmVzDQoNCiMjIyBSZWdyZXNpw7NuIGxpbmVhbA0KDQpFbiBlc3RlIGNhc28gZXN0YW1vcyBtb2RlbGFuZG8gbGEgcHJvYmFiaWxpZGFkIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6IA0KDQokUChYKT0gXGJldGFfMCArIFxzdW1cbGltaXRzX3tqPTF9XnAgXGJldGFfaiBYJA0KDQpWZWFtb3MgcXVlIHRhbiBidWVubyBlcyBlbCBtb2RlbG8gbGluZWFsIHBhcmEgZXN0bywgdXNhbmRvIGxhIGVkYWQgY29tbyBwcmVkaWN0b3IuDQoNCmBgYHtyfQ0KbXJsIDwtIGRmdGl0YW5pY190cmFpbiAlPiUgDQogICAgICAgICAgICAgIGxtKGZvcm11bGEgPSBTdXJ2aXZlZCB+IEFnZSkgDQp0ZHkgPSBtcmwgJT4lIHRpZHkoKSANCnRkeQ0KbXJsICU+JSBnbGFuY2UoKQ0KYGBgDQoNCkxvcyBlc3RpbWFkb3JlcyBzb24gc2lnbmlmaWNhdGl2b3MgeSBlbCB0ZXN0IGRlIHNpZ25pZmljYXRpdmlkYWQgZ2xvYmFsIGRlbCBtb2RlbG8gdGFtYmnDqW4gZXMgc2lnbmlmaWNhdGl2by4NCg0KVmVhbW9zIHVuIGdyw6FmaWNvIGRlIG51ZXN0cm8gbW9kZWxvLg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmdncGxvdChkZnRpdGFuaWNfdHJhaW4sIGFlcyhBZ2UsIFN1cnZpdmVkKSkgKyANCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9ZmFjdG9yKFN1cnZpdmVkKSkpICsNCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IHRkeSRlc3RpbWF0ZVsxXSwgc2xvcGUgPSB0ZHkkZXN0aW1hdGVbMl0sIGNvbG9yPSdmb3Jlc3RncmVlbicsIHNpemU9MikgKyANCiAgbGFicyh0aXRsZT0iTW9kZWxvIExpbmVhbCBTaW1wbGUiLCBjb2xvcj0nQ2xhc2UnKSArDQogIGxpbXMoeT1jKC0xLDIpKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNClBhcmVjZSB0ZW5lciBiYXN0YW50ZXMgcHJvYmxlbWFzIHBhcmEgZXN0aW1hciBsYSBwcm9iYWJpbGlkYWQgZGUgc3VwZXJ2aXZlbmNpYSBkZSBsb3MgaW5kaXZpZHVvOiBubyBleGlzdGUgdW4gcHVudG8gZGUgY29ydGUgY2xhcm8sIGxhIHByZWRpY2Npw7NuIHBvZHLDrWEgc2VyIG1heW9yIGEgMSBvIG1lbm9yIGEgY2VybyBsbGVnYWRvIGVsIGNhc28uDQoNCiMjIyBSZWdyZXNpw7NuIExvZ8Otc3RpY2ENCg0KUGFyYSBldml0YXIgZXN0b3MgcHJvYmxlbWFzLCB1c2Ftb3MgbGEgKipmdW5jacOzbiBsb2fDrXN0aWNhKiouDQoNCiRQKFk9MXxYKT0gXGZyYWN7ZV57XGJldGFfMCArIFxzdW1cbGltaXRzX3tqPTF9XnAgXGJldGFfaiBYfX17MStlXntcYmV0YV8wICsgXHN1bVxsaW1pdHNfe2o9MX1ecCBcYmV0YV9qIFh9fSQNCg0KRWwgbGFkbyBkZXJlY2hvIHNlIGxsYW1hICoqZXhwaXQqKg0KDQpFc3RhIGZ1bmNpw7NuIGFjb3RhIGVsIHJlc3VsdGFkbyBlbnRyZSAwIHkgMSwgbG8gY3VhbCBlcyBtdWNobyBtw6FzIGFkZWN1YWRvIHBhcmEgbW9kZWxhciB1bmEgcHJvYmFiaWxpZGFkLg0KDQpMdWVnbyBkZSBoYWNlciBhbGd1bmFzIG9wZXJhY2lvbmVzLCBwb2RlbW9zIGxsZWdhciBhIGxhIGV4cHJlc2nDs246DQoNCiRcbG9nIHtcZnJhY3tQKHgpfXsxLVAoeCl9fT0gXGJldGFfMCArIFxzdW1cbGltaXRzX3tqPTF9XnAgXGJldGFfaiBYJA0KDQpFbCBsYWRvIGl6cXVpZXJkbyBlcyBlbCBsb2dhcml0bW8gZGUgbG9zICoqb2RkcyoqIHkgc2UgbGxhbWEgKipsb2dpdCoqLg0KDQojIyMgTW9kZWxvIA0KDQpMYSBmdW5jw63Ds24gYGdsbSgpYCBub3MgcGVybWl0ZSBjcmVhciB1biBtb2RlbG8gbGluZWFsIGdlbmVyYWxpemFkbyAoR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVsKS4gQWwgaWd1YWwgcXVlIGxhIGZ1bmNpw7NuIGBsbSgpYCB0b21hIGNvbW8gYXJndW1lbnRvcyB1bmEgKipmb3JtdWxhKiogeSBsb3MgKipkYXRvcyoqIHBlcm8gdGFtYmnDqW4gc2UgZGViZSBlc3BlY2lmaWNhciBlbCBhcmd1bWVudG8gKipmYW1pbHkqKjogaW5kaWNhbW9zIGxhIGRpc3RyaWJ1Y2nDs24gZGVsIGVycm9yIHkgbGEgZnVuY2nDs24gbGluayBxdWUgdmFtb3MgYSB1dGlsaXphciBlbiBlbCBtb2RlbG8uIA0KDQpBbGd1bmFzIGZhbWlsaWFzIHNvbjoNCg0KKiAqQmlub21pYWwqOiBsaW5rPWxvZ2l0DQoNCiogKlBvaXNzb24qOiBsaW5rPWxvZw0KDQoqICpHYXVzc2lhbmEqOiBsaW5rPWlkZW50aWRhZA0KDQpDb21vIGVzdGFtb3MgdHJhYmFqYW5kbyBjb24gdW4gZmVuw7NtZW5vIHF1ZSBzdXBvbmVtb3MgdGllbmUgdW5hIGRpc3RyaWJ1Y2nDs24gYmlub21pYWwsIGFzw60gbG8gZXNwZWNpZmljYW1vcyBlbiBlbCBwYXLDoW1ldHJvICoqZmFtaWx5KiouDQoNClJlYWxpemFtb3MgdW4gbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbG9nw61zdGljYSBwYXJhIHByZWRlY2lyIGxhIHN1cGVydml2ZW5jaWEgZW4gZnVuY2nDs24gZGUgKipQY2xhc3MqKiwgKipTZXgqKiB5ICoqQWdlKiouIA0KDQpgYGB7cn0NCiMgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbG9nw61zdGljYSANCmdsbTEgPC0gZ2xtKGRhdGEgPSBkZnRpdGFuaWNfdHJhaW4sIFN1cnZpdmVkIH4gUGNsYXNzICsgU2V4ICsgQWdlLCBmYW1pbHkgPSAnYmlub21pYWwnKQ0KIyB2ZW8gbG9zIHJlc3VsdGFkb3MNCnRpZHkoZ2xtMSkNCmdsYW5jZShnbG0xKQ0KYGBgDQoNClNlIHZhIGEgZGlzY3V0aXIgc29icmUgbGEgaW50ZXJwcmV0YWNpw7NuIGRlIGNvZWZpY2llbnRlcyB5IGxhIGV2YWx1YWNpw7NuIGVuIGxhcyBzaWd1aWVudGVzIHNlY2Npb25lcy4NCg0KIyMjIENyZWFjacOzbiBkZSBmw7NybXVsYXMNCg0KUGFyYSBjcmVhciB2YXJpb3MgbW9kb2Vsb3MgZGUgcmVncmVzacOzbiBsb2fDrXN0aWNhIHBvZGVtb3MgdXRpbGl6YXIgbGEgZnVuY2nDs24gYGZvcm11bGFzYCBkZWwgcGFxdWV0ZSAqKm1vZGVscioqIHBhcmEgY3JlYXIgdW4gb2JqZXRvIHF1ZSBjb250aWVuZSB0b2RhcyBsYXMgZsOzcm11bGFzIHF1ZSB2YW1vcyBhIHV0aWxpemFyLiANCg0KRW4gYC5yZXNwb25zZWAgZXNwZWNpZmljYW1vcyBsYSB2YXJpYWJsZSByZXNwdWVzdGEgZGUgbnVlc3RyYXMgZsOzcm11bGFzIHkgbHVlZ28gbm9tYnJhbW9zIGxhcyBmw7NybXVsYXMgcXVlIHF1ZXJhbW9zIGFybWFyLg0KDQpBc8OtLCBhcm1hcmVtb3MgZGlzdGludG9zIG1vZGVsb3MgY29tYmluYW5kbyBkaXN0aW50YXMgdmFyaWFibGVzLiBTZSBnZW5lcmFuIDcgbW9kZWxvcyBkaXN0aW50b3MgcGFyYSBwcmVkZWNpciBsYSBzdXBlcnZpdmVuY2lhIGVuIGZ1bmNpw7NuIGRlIGRpc3RpbnRhcyBjb21iaW5hY2lvbmVzIGRlIGxhcyB2YXJpYWJsZXMgcGNsYXNzLCBzZXgsIGFnZSB5IGZhcmUuIA0KDQpgYGB7cn0NCiMgQ3JlYWNpw7NuIGRlIGbDs3JtdWxhcw0KbG9naXRfZm9ybXVsYXMgPC0gZm9ybXVsYXMoLnJlc3BvbnNlID0gfiBTdXJ2aXZlZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzID0gfiBQY2xhc3MsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V4ID0gfiBTZXgsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgYWdlID0gfiBBZ2UsICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIFBjUyA9IH4gUGNsYXNzICsgU2V4LCAjIG1vZGVsbyBjb24gbGFzIHZhcmlhYmxlcyBxdWUgbcOhcyBwYXJlY2VuIGRpdmlkaXIgZWwgdGFyZ2V0DQogICAgICAgICAgICAgICAgICAgICAgICAgICBQY1NBID0gfiBQY2xhc3MgKyBTZXggKyBBZ2UsICMgbW9kZWxvIGdsbTENCiAgICAgICAgICAgICAgICAgICAgICAgICAgIFBjU0YgPSB+IFBjbGFzcyArIFNleCArIEZhcmUsICAjIG1vZGVsbyBwcmV2aW8gc2luIEFnZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgUGNTQUYgPSB+IFBjbGFzcyArIFNleCArIEFnZSArIEZhcmUgIyBtb2RlbG8gZ2xtMSBjb24gRmFyZSAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICApDQpsb2dpdF9mb3JtdWxhcyAjIG9ic2VydmFtb3MgZWwgb2JqZXRvIGZvcm11bGFzDQpgYGANCg0KIyMjIENyZWFjacOzbiBkZSBtb2RlbG9zDQoNClByb2NlZGVtb3MgYSBjcmVhciBsb3MgbW9kZWxvcyBhIHBhcnRpciBkZSBlc3RhcyBmw7NybXVsYXMuDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KbW9kZWxzIDwtIGRhdGFfZnJhbWUobG9naXRfZm9ybXVsYXMpICU+JSAjIGRhdGFmcmFtZSBhIHBhcnRpciBkZWwgb2JqZXRvIGZvcm11bGFzDQogIG11dGF0ZShtb2RlbHMgPSBuYW1lcyhsb2dpdF9mb3JtdWxhcyksICMgY29sdW1uYSBjb24gbG9zIG5vbWJyZXMgZGUgbGFzIGZvcm11bGFzDQogICAgICAgICBleHByZXNzaW9uID0gcGFzdGUobG9naXRfZm9ybXVsYXMpLCAjIGNvbHVtbmEgY29uIGxhcyBleHByZXNpb25lcyBkZSBsYXMgZm9ybXVsYXMNCiAgICAgICAgIG1vZCA9IG1hcChsb2dpdF9mb3JtdWxhcywgfmdsbSguLCBmYW1pbHkgPSAnYmlub21pYWwnLCBkYXRhID0gZGZ0aXRhbmljX3RyYWluKSkpICMgUXVlIGVzdGFtb3MgaGFjaWVuZG8gYWPDoT8gUXVlIHZhbW9zIGEgZW5jb250cmFyIGVuIGxhIGNvbHVtbmE/DQptb2RlbHMNCmBgYA0KDQojIyMgTW9kZWxvcyBzaW1wbGVzDQoNClByb2JhbW9zIGxvcyBwcmltZXJvcyB0cmVzIG1vZGVsb3MsIGFxdWVsbG9zIHF1ZSB0aWVuZW4gdW4gw7puaWNvIHByZWRpY3Rvci4gVXNhbW9zIGxhIGZ1bmNpw7NuIF90aWR5XyBwYXJhIG9idGVuZXIgbG9zIHBhcsOhbWV0cm9zIGVzdGltYWRvcyBwYXJhIGVzdG9zIHRyZXMgbW9kZWxvcy4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQptb2RlbHMgJT4lIA0KICBmaWx0ZXIobW9kZWxzICVpbiUgYygnY2xhc3MnLCdzZXgnLCdhZ2UnKSkgJT4lDQogIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCwgdGlkeSkpICAjIFF1w6kgcmVhbGl6YW1vcyBlbiBlc3RlIHBhc28/IFF1ZSB2YSBhIHRlbmVyIGVzdGEgY29sdW1uYT8NCmBgYA0KDQpQYXJhIGFjY2VkZXIgYSBsb3MgZWxlbWVudG9zIGRlIGxhIG51ZXZhIGNvbHVtbmEgX3RpZHlfIGRlYmVtb3MgZGVzYW5pZGFybGEgKHVzYW5kbyBgdW5uZXN0KClgKS4gRWwgYW5pZGFkbyBjcmVhIHVuYSBjb2x1bW5hIGRlIGxpc3RhcyBkZSBkYXRhZnJhbWVzLCBlcyBpbXBsw61jaXRhbWVudGUgdW5hIG9wZXJhY2nDs24gZGUgcmVzdW1lbjogb2J0aWVuZSB1bmEgZmlsYSBwYXJhIGNhZGEgZ3J1cG8gZGVmaW5pZG8gcG9yIGxhcyBjb2x1bW5hcyBubyBhbmlkYWRhcy4gRGVzYW5pZGFyIGxvIGFwbGFuYSBkZSBudWV2byBlbiBjb2x1bW5hcyByZWd1bGFyZXMuIFBhcmEgbWF5b3IgZGV0YWxsZSwgc3VnZXJpbW9zIHZlciBsYSBkb2N1bWVudGFjacOzbiBkZSB0aWR5ciBzb2JyZSBbTmVzdCBhbmQgdW5uZXN0XShodHRwczovL3RpZHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL25lc3QuaHRtbCkuIA0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCm1vZGVscyAlPiUgDQogIGZpbHRlcihtb2RlbHMgJWluJSBjKCdjbGFzcycsJ3NleCcsJ2FnZScpKSAlPiUNCiAgbXV0YXRlKHRpZHkgPSBtYXAobW9kLCB0aWR5KSkgJT4lICAjIFF1w6kgcmVhbGl6YW1vcyBlbiBlc3RlIHBhc28/IFF1ZSB2YSBhIHRlbmVyIGVzdGEgY29sdW1uYT8NCiAgdW5uZXN0KHRpZHkpICU+JSANCiAgbXV0YXRlKGVzdGltYXRlPXJvdW5kKGVzdGltYXRlLDUpLCAjIHJlZG9uZGVhbW9zIHZhbG9yZXMgcGFyYSBmYWNpbGl0YXIgbGVjdHVyYQ0KICAgICAgICAgcC52YWx1ZT1yb3VuZChwLnZhbHVlLDQpKQ0KYGBgDQoNCk9ic2VydmFtb3MgcXVlIHRvZG9zIGxvcyBtb2RlbG9zIHRpZW5lbiBjb2VmaWNpZW50ZXMgc2lnbmlmaWNhdGl2b3MgYXVucXVlIGVsIGRlIEFnZSBzZSBlbmN1ZW50cmEgbXV5IGNlcmNhbm8gYSBudWVzdHJvIHZhbG9yIGRlIHJlY2hhem8uDQoNCiMjIyMgSW50ZXJwcmV0YWNpw7NuIGRlIGxvcyBjb2VmaWNpZW50ZXMNCg0KUmVjb3JkYW5kbyBsYSBlY3VhY2nDs24gcGFyYSBtb2RlbGFyIGxhIHByb2JhYmlsaWRhZDoNCg0KJFAoWT0xfFgpPSBcZnJhY3tlXntcYmV0YV8wICsgXHN1bVxsaW1pdHNfe2o9MX1ecCBcYmV0YV9qIFh9fXsxK2Vee1xiZXRhXzAgKyBcc3VtXGxpbWl0c197aj0xfV5wIFxiZXRhX2ogWH19JA0KDQpTZSBvYnNlcnZhIHF1ZSBhaG9yYSBsYXMgdmFyaWFibGVzIHlhIG5vIHRpZW5lbiB1bmEgcmVsYWNpw7NuIGxpbmVhbCBjb24gbGEgcHJvYmFiaWxpZGFkLiBFbiBlc3RlIG1vZGVsbyB1biBjb2VmaWNpZW50ZSBwb3NpdGl2byBpbmRpY2EgcXVlIGZyZW50ZSBhIGF1bWVudG9zIGRlIGRpY2hhIHZhcmlhYmxlIGxhIHByb2JhYmlsaWRhZCBhdW1lbnRhLCBtaWVudHJhcyBxdWUgdW4gY29lZmljaWVudGUgbmVnYXRpdm8gbm9zIGluZGljYSBsbyBjb250cmFyaW8uIFBhcmEgbnVlc3Ryb3MgbW9kZWxvczoNCg0KKipNb2RlbG8gY2xhc3MqKg0KDQoqIM6yMCA9IGByIHJvdW5kKChtb2RlbHMgJT4lIGZpbHRlcihtb2RlbHMgJWluJSBjKCdjbGFzcycpKSAlPiUgbXV0YXRlKHRpZHkgPSBtYXAobW9kLCB0aWR5KSkgJT4lICB1bm5lc3QodGlkeSkgJT4lIGZpbHRlcih0ZXJtID09ICcoSW50ZXJjZXB0KScpICU+JSBzZWxlY3QoZXN0aW1hdGUpKSRlc3RpbWF0ZSw1KWAgY29ycmVzcG9uZGUgYSBwZXJzb25hcyBxdWUgdmlhamFyb24gZW4gcHJpbWVyYSBjbGFzZS4NCg0KKiDOsjEgPSBgciByb3VuZCgobW9kZWxzICU+JSBmaWx0ZXIobW9kZWxzICVpbiUgYygnY2xhc3MnKSkgJT4lIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCwgdGlkeSkpICU+JSAgdW5uZXN0KHRpZHkpICU+JSBmaWx0ZXIodGVybSA9PSAnUGNsYXNzMicpICU+JSBzZWxlY3QoZXN0aW1hdGUpKSRlc3RpbWF0ZSw1KWAgeSDOsjIgPSBgciByb3VuZCgobW9kZWxzICU+JSBmaWx0ZXIobW9kZWxzICVpbiUgYygnY2xhc3MnKSkgJT4lIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCwgdGlkeSkpICU+JSAgdW5uZXN0KHRpZHkpICU+JSBmaWx0ZXIodGVybSA9PSAnUGNsYXNzMycpICU+JSBzZWxlY3QoZXN0aW1hdGUpKSRlc3RpbWF0ZSw1KWAgY29ycmVzcG9uZGVuIGEgcGVyc29uYXMgcXVlIHZpYWphcm9uIGVuIGxhIHNlZ3VuZGEgeSB0ZXJjZXJhIGNsYXNlLCByZXNwZWN0aXZhbWVudGUuIEVsIGNvZWZpY2llbnRlIGVzdGltYWRvIGVuIGFtYm9zIGNhc29zIGZ1ZSBuZWdhdGl2bywgaW5kaWNhbmRvIHF1ZSBsYSAqKnByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIGRpc21pbnV5ZSoqIGVuIGNvbXBhcmFjacOzbiBjb24gbGEgcHJpbWVyYSBjbGFzZS4gIEFsIHNlciBtw6FzIG5lZ2F0aXZvIGVsIGNvZWZpY2llbnRlIGVzdGltYWRvIGRlIFBjbGFzczMsIHNlIGVzcGVyYSBxdWUgbGEgcHJvYmFiaWxpZGFkIGRlIFN1cGVydml2ZW5jaWEgc2VhIG1lbm9yIGHDum4gcGFyYSBlc3RhIGNsYXNlIHF1ZSBwYXJhIHVuIHBhc2FqZXJvIGVuIGNsYXNlIDIuDQoNCioqTW9kZWxvIHNleCoqDQoNCiogzrIwID0gYHIgcm91bmQoKG1vZGVscyAlPiUgZmlsdGVyKG1vZGVscyAlaW4lIGMoJ3NleCcpKSAlPiUgbXV0YXRlKHRpZHkgPSBtYXAobW9kLCB0aWR5KSkgJT4lICB1bm5lc3QodGlkeSkgJT4lIGZpbHRlcih0ZXJtID09ICcoSW50ZXJjZXB0KScpICU+JSBzZWxlY3QoZXN0aW1hdGUpKSRlc3RpbWF0ZSw1KWAgY29ycmVzcG9uZGUgYSBtdWplcmVzIHF1ZSB2aWFqYXJvbiBhIGJvcmRvIGRlbCB0aXRhbmljLg0KDQoqIM6yMSA9IGByIHJvdW5kKChtb2RlbHMgJT4lIGZpbHRlcihtb2RlbHMgJWluJSBjKCdzZXgnKSkgJT4lIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCwgdGlkeSkpICU+JSAgdW5uZXN0KHRpZHkpICU+JSBmaWx0ZXIodGVybSA9PSAnU2V4bWFsZScpICU+JSBzZWxlY3QoZXN0aW1hdGUpKSRlc3RpbWF0ZSw1KWAgcmVwcmVzZW50YSBhIGxvcyBob21icmVzIHF1ZSB2aWFqYXJvbiBlbiBlbCB0aXRhbmljIGUgaW5kaWNhIHF1ZSAqKmxhIHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIHNlIHJlZHVjZSoqIGVuIGNvbXBhcmFjacOzbiBhIGxhcyBtdWplcmVzLg0KDQoqKk1vZGVsbyBhZ2UqKg0KDQoqIM6yMSA9IGByIHJvdW5kKChtb2RlbHMgJT4lIGZpbHRlcihtb2RlbHMgJWluJSBjKCdhZ2UnKSkgJT4lIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCwgdGlkeSkpICU+JSAgdW5uZXN0KHRpZHkpICU+JSBmaWx0ZXIodGVybSA9PSAnQWdlJykgJT4lIHNlbGVjdChlc3RpbWF0ZSkpJGVzdGltYXRlLDUpYCBpbmRpY2EgcXVlIGxhICoqcHJvYmFiaWxpZGFkIGRlIHN1cGVydml2ZW5jaWEgZGlzbWludXllKiogcG9yIGNhZGEgYcOxbyBtw6FzIGRlIGVkYWQgZGUgbGEgcGVyc29uYS4NCg0KIyMjIMK/UXXDqSBwYXNhcsOtYSBlbiB1biBtb2RlbG8gbcO6bHRpcGxlPw0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCm1vZGVscyAlPiUgDQogIGZpbHRlcihtb2RlbHMgJWluJSBjKCdQY1NBJykpICU+JQ0KICBtdXRhdGUodGlkeSA9IG1hcChtb2QsIHRpZHkpKSAlPiUgDQogIHVubmVzdCh0aWR5KSAlPiUgDQogIG11dGF0ZShlc3RpbWF0ZT1yb3VuZChlc3RpbWF0ZSw1KSwNCiAgICAgICAgIHAudmFsdWU9cm91bmQocC52YWx1ZSw0KSkNCmBgYA0KQXPDrSBjb21vIHNlIG9ic2VydsOzIGVuIGVsIG1vZGVsbyBzaW1wbGUgcGFyYSBsYSB2YXJpYWJsZSBQY2xhc3MsIGVuIGVzdGUgY2FzbyBsb3MgY29lZmljaWVudGVzIGVzdGltYWRvcyBkZSBQY2xhc3MyIHkgUGNsYXNzMyByZXN1bHRhbiBuZWdhdGl2b3MsIGluZGljYW5kbyBxdWUgc2kgZWwgcGFzYWplcm8gcGVydGVuZWNlIGEgc2VndW5kYSBvIHRlcmNlcmEgY2xhc2UsIHN1IHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmllbmNpYSBlc3BlcmFkYSBkaXNtaW51eWUgcmVzcGVjdG8gZGUgdW5hIHBlcnNvbmEgZGUgcHJpbWVyYSBjbGFzZSwgZGFkYXMgbGFzIGRlbcOhcyB2YXJpYWJsZXMgZGVsIG1vZGVsbyAoU2V4IHkgQWdlKS4gDQoNClBhcmEgbGEgdmFyaWFibGUgU2V4IG9ic2VydmFtb3MgcXVlIGVsIGNvZWZpY2llbnRlIFNleE1hbGUgcmVzdWx0YSBuZWdhdGl2bzsgZXN0byBzaWduaWZpY2EgcXVlIGxhIHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIGVzcGVyYWRhIGRlIGxvcyBob21icmVzIGRpc21pbnV5ZSByZXNwZWN0byBhIGxhIGRlIGxhcyBtdWplcmVzLCBkYWRhcyBsYXMgZGVtw6FzIHZhcmlhYmxlcyBkZWwgbW9kZWxvLg0KDQpQYXJhIGxhIHZhcmlhYmxlIEFnZSwgZWwgY29lZmljaWVudGUgZXN0aW1hZG8gdGFtYmnDqW4gcmVzdWx0YSBuZWdhdGl2bywgaW5kaWNhbmRvIHF1ZSBhbnRlIHVuIGF1bWVudG8gZW4gbGEgZWRhZCBkZWwgcGFzYWplcm8sIHN1IHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIGVzcGVyYWRhIGRpc21pbnV5ZSwgZGFkYXMgbGFzIGRlbcOhcyB2YXJpYWJsZXMuDQoNCkFzaW1pc21vLCBzZSBvYnNlcnZhIHF1ZSBsb3MgY29lZmljaWVudGVzIHJlc3VsdGFuIHRvZG9zIHNpZ25pZmljYXRpdm9zIGVuIGVzdGUgbW9kZWxvIChwLXZhbG9yPDAuMDUpLiANCg0KUmVjb21lbmRhbW9zIGxlZXIgZWwgW2NhcMOtdHVsbyBkZSByZWdyZXNpw7NuIGxvZ8Otc3RpY2FdKGh0dHBzOi8vY2hyaXN0b3BobS5naXRodWIuaW8vaW50ZXJwcmV0YWJsZS1tbC1ib29rL2xvZ2lzdGljLmh0bWwpIGRlICpJbnRlcnByZXRhYmxlIE1hY2hpbmUgTGVhcm5pbmc6IEEgR3VpZGUgZm9yIE1ha2luZyBCbGFjayBCb3ggTW9kZWxzIEV4cGxhaW5hYmxlKiBkZSBNb2xuYXIgQ3Jpc3RvcGggcGFyYSB1bmEgZGlzY3VzacOzbiBtw6FzIHByb2Z1bmRhIGRlIGxhIGludGVycHJldGFjacOzbiBkZSB1biBtb2RlbG8gZGUgcmVncmVzacOzbiBsb2fDrXN0aWNhLg0KDQojIyMgRXZhbHVhY2nDs24gZGUgdG9kb3MgbG9zIG1vZGVsb3MNCg0KQ29uIGBtYXAoKWAgYWdyZWdhbW9zIGxhIGZ1bmNpw7NuIGBnbGFuY2VgIHBhcmEgdHJhZXIgaW5mb3JtYWNpw7NuIHJlbGV2YW50ZSBwYXJhIGxhIGV2YWx1YWNpw7NuIGRlbCBtb2RlbG8uIENvbiBgdW5uZXN0KClgIGFjY2VkZW1vcyBhIGRpY2hhIGluZm9ybWFjacOzbi4gUG9yIMO6bHRpbW8sIGFncmVnYW1vcyB1bmEgY29sdW1uYSBjb24gZWwgcG9yY2VudGFqZSBkZSBkZXZpYW5jZSBleHBsaWNhZG8gcG9yIGNhZGEgbW9kZWxvIHkgb3JkZW5hbW9zIGVsIGRhdGFzZXQgc2Vnw7puIHN1IHZhbG9yIGRlIGRldmlhbmNlLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMgQ2FsY3VsYXIgbGFzIG1lZGlkYXMgZGUgZXZhbHVhY2nDs24gcGFyYSBjYWRhIG1vZGVsbw0KbW9kZWxzIDwtIG1vZGVscyAlPiUgDQogIG11dGF0ZShnbGFuY2UgPSBtYXAobW9kLGdsYW5jZSkpDQojIE9idGVuZXIgbGFzIG1lZGlkYXMgZGUgZXZhbHVhY2lvbiBkZSBpbnRlcmVzDQptb2RlbHMgJT4lIA0KICB1bm5lc3QoZ2xhbmNlKSAlPiUNCiAgIyBDYWxjdWxhbW9zIGxhIGRldmlhbmNlIGV4cGxpY2FkYQ0KICBtdXRhdGUocGVyY19leHBsYWluZWRfZGV2ID0gMS1kZXZpYW5jZS9udWxsLmRldmlhbmNlKSAlPiUgDQogIHNlbGVjdCgtYyhtb2RlbHMsIGRmLm51bGwsIEFJQywgQklDKSkgJT4lIA0KICBhcnJhbmdlKGRldmlhbmNlKQ0KYGBgDQoNCkxvcyBtb2RlbG9zIHF1ZSBpbmNsdXllbiBsYXMgMyB2YXJpYWJsZXMgUGNsYXNzLCBTZXggeSBBZ2UgcGFyZWNlbiBzZXIgbG9zIHF1ZSBtaW5pbWl6YW4gbGEgZGV2aWFuY2UuIE9ic2VydmFtb3MgdGFtYmnDqW4gcXVlIGxvcyAyIMO6bHRpbW9zIG1vZGVsb3MgcmVkdWNlbiBtdXkgcG9jbyBsYSBkZXZpYW5jZSByZXNwZWN0byBhIGxhIGRldmlhbmNlIG51bGEuDQoNCiMjIyBHcsOhZmljb3MgZGUgRXZhbHVhY2nDs24NCg0KUmVhbGl6YW1vcyBsb3MgZ3LDoWZpY29zIHBhcmEgZWwgbW9kZWxvIGNvbXBsZXRvIHkgdW5vIGRlIGxvcyBtb2RlbG9zIGNvbiBtYXlvciBkZXZpYW5jZSAoQWdlKS4NCg0KQ29tZW56YW1vcyBhZ3JlZ2FuZG8gbGFzIHByZWRpY2Npb25lcyBjb24gYGF1Z21lbnRgIGNvbiBlbCBwYXLDoW1ldHJvIGB0eXBlPSJyZXNwb25zZSJgLiBMYSBmdW5jacOzbiBhdWdtZW50IGhlcmVkYSBlbCBhcmd1bWVudG8gdHlwZS5wcmVkaWN0IGRlIGxhIGZ1bmNpw7NuIHByZWRpY3QuDQoNCiAgKiBTaSBgdHlwZS5wcmVkaWN0ID0gJ2xpbmsnYCBsYSBwcmVkaWNjacOzbiBlcyBlbiB0w6lybWlub3MgZGUgbGEgZnVuY2nDs24gbGluay4gRW4gbnVlc3RybyBjYXNvIHNvbiBlbCBsb2dhcml0bW8gZGUgbGFzIG9kZHMsIGVzIGRlY2lyLCBsb3MgdmFsb3JlcyBxdWUgdG9tYSBsYSBleHByZXNpw7NuIGxvZ2l0Lg0KICANCiAgKiBTaSBgdHlwZS5wcmVkaWN0ID0gJ3Jlc3BvbnNlJ2AgbGEgcHJlZGljY2nDs24gc29uIGxhcyBwcm9iYWJpbGlkYWRlcyBkZSBxdWUgbGEgb2JzZXJ2YWNpw7NuIHBlcnRlbmV6Y2EgYSBsYSBjbGFzZSBwb3NpdGl2YS4gRW4gbnVlc3RybyBjYXNvLCBkZXZ1ZWx2ZSBsYSBwcm9iYWJpbGlkYWQgZGUgbGEgcXVlIHBlcnNvbmEgc29icmV2aXZhLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMgQcOxYWRpciBsYXMgcHJlZGljY2lvbmVzDQptb2RlbHMgPC0gbW9kZWxzICU+JSANCiAgbXV0YXRlKHByZWQ9IG1hcChtb2QsIGF1Z21lbnQsIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpKQ0KI09ic2VydmFjaW9uZXMgY29uIHByb2JhYmlsaWRhZCBtw6FzIGJhamENCm1vZGVscyRwcmVkJFBjU0FGICU+JSBhcnJhbmdlKC5maXR0ZWQpICU+JSBoZWFkKDEwKQ0KI09ic2VydmFjaW9uZXMgY29uIHByb2JhYmlsaWRhZCBtw6FzIGFsdGENCm1vZGVscyRwcmVkJFBjU0FGICU+JSBhcnJhbmdlKGRlc2MoLmZpdHRlZCkpICU+JSBoZWFkKDEwKQ0KYGBgDQoNCkd1YXJkYW1vcyBsYXMgcHJlZGljY2lvbmVzIHBhcmEgbG9zIG1vZGVsb3MgbWVuY2lvbmFkb3MuDQoNCmBgYHtyfQ0KIyBNb2RlbG8gY29tcGxldG8NCnByZWRpY3Rpb25fZnVsbCA8LSBtb2RlbHMgJT4lIA0KICBmaWx0ZXIobW9kZWxzPT0iUGNTQUYiKSAlPiUgDQogIHVubmVzdChwcmVkKQ0KI01vZGVsbyBtYWxvDQpwcmVkaWN0aW9uX2JhZCA8LSBtb2RlbHMgJT4lIA0KICBmaWx0ZXIobW9kZWxzPT0iYWdlIikgJT4lIA0KICB1bm5lc3QocHJlZCkNCmBgYA0KDQoNCiMjIyMgVmlvbGluIHBsb3RzDQoNCmBgYHtyfQ0KIyBncmFmaWNhbW9zIGVsIG1vZGVsbyBjb21wbGV0bw0KdmlvbGluX2Z1bGwgPSBnZ3Bsb3QocHJlZGljdGlvbl9mdWxsLCBhZXMoeD1TdXJ2aXZlZCwgeT0uZml0dGVkLCBncm91cD1TdXJ2aXZlZCwgZmlsbD1mYWN0b3IoU3Vydml2ZWQpKSkgKyANCiAgZ2VvbV92aW9saW4oKSArDQogIHRoZW1lX2J3KCkgKw0KICBndWlkZXMoc2NhbGU9Im5vbmUiKSArDQogIGxhYnModGl0bGU9J1Zpb2xpbiBwbG90Jywgc3VidGl0bGU9J01vZGVsbyBjb21wbGV0bycsIHk9J1ByZWRpY3RlZCBwcm9iYWJpbGl0eScpDQojIGdyYWZpY2Ftb3MgZWwgbW9kZWxvIG1hbG8NCnZpb2xpbl9iYWQgPSBnZ3Bsb3QocHJlZGljdGlvbl9iYWQsIGFlcyh4PVN1cnZpdmVkLCB5PS5maXR0ZWQsIGdyb3VwPVN1cnZpdmVkLCBmaWxsPWZhY3RvcihTdXJ2aXZlZCkpKSArIA0KICBnZW9tX3Zpb2xpbigpICsgDQogIHRoZW1lX2J3KCkgKw0KICBndWlkZXMoc2NhbGU9Im5vbmUiKSArDQogIGxhYnModGl0bGU9J1Zpb2xpbiBwbG90Jywgc3VidGl0bGU9J01vZGVsbyBtYWxvJywgeT0nUHJlZGljdGVkIHByb2JhYmlsaXR5JykNCiMgbW9zdHJhbW9zIGFtYm9zDQpwbG90X2dyaWQodmlvbGluX2JhZCwgdmlvbGluX2Z1bGwpDQpgYGANCg0KRW4gbG9zIGdyw6FmaWNvcyBkZSB2aW9saW4gb2JzZXJ2YW1vczoNCg0KICAqIEVuIGVsIGVqZSBkZSBhYnNjaXNhcyBsYSBjbGFzZSB2ZXJkYWRlcmE6IFN1cnZpdmVkIG8gTm8gU3Vydml2ZWQgKDEgbyAwKS4NCiAgDQogICogRW4gZWwgZWplIGRlIG9yZGVuYWRhcyBsYSBwcm9iYWJpbGlkYWQgcHJlZGljaGEgcG9yIG51ZXN0cm8gbW9kZWxvLg0KICANCiAgKiBFbCBncsOhZmljbyBub3MgbXVlc3RyYSBsYSBkaXN0cmlidWNpw7NuIGRlIGxhIGNhbnRpZGFkIGRlIG9ic2VydmFjaW9uZXMgcG9yIHN1IGNsYXNlIHJlYWwgeSBsYSBwcm9iYWJpbGlkYWQgcXVlIGxlIGFzaWduYSBudWVzdHJvIG1vZGVsby4NCg0Kwr9DdcOhbCBwYXJlY2Ugc2VyIHVuIHB1bnRvIGRlIGNvcnRlIGFkZWN1YWRvIHBhcmEgY2FkYSBtb2RlbG8/DQoNCiMjIyMgR3LDoWZpY28gZGUgSG9zbWVyLUxlbWVzaG93DQoNClNlIGdlbmVyYSB1bmEgZnVuY2nDs24gcGFyYSByZWFsaXphciB1biBncsOhZmljbyBkZSBIb3NtZXItTGVtZXNob3cgcGFyYSB1biBkYXRhc2V0LiBQYXJhIGVsbG8gc2UgZmlqYW4gbG9zIHNpZ3VpZW50ZXMgcGFyw6FtZXRyb3M6IA0KICANCiogZGF0YXNldDogY29uanVudG8gZGUgZGF0b3MNCg0KKiBwcmVkaWN0ZWRfY29sdW1uOiBjb2x1bW5hIGNvbiBsYSBwcm9iYWJpbGlkYWQgcHJlZGljaGENCg0KKiBjbGFzc19jb2x1bW46IGNvbHVtbmEgY29uIGxhIGNsYXNlIGEgcHJlZGVjaXINCg0KKiBwb3NzaXRpdmVfdmFsdWU6IHZhbG9yIGRlIGxhIGNsYXNlIGEgcHJlZGVjaXINCg0KKiBiaW5zOiBjYW50aWRhZCBkZSBncnVwb3MgZGVsIGdyw6FmaWNvDQoNCiogY29sb3I6IGNvbG9yIGRlIGxvcyBwdW50b3MNCg0KKiBudWRnZV94OiBkZXNwbGF6YW1pZW50byBkZSBsYSBldGlxdWV0YSBlbiBlbCBlamUgeA0KDQoqIG51ZGdlX3k6IGRlc3BsYXphbWllbnRvIGRlIGxhIGV0aXF1ZXRhIGVuIGVsIGVqZSB5DQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KSG9zbWVyX0xlbWVzaG93X3Bsb3QgPC0gZnVuY3Rpb24oZGF0YXNldCwgcHJlZGljdGVkX2NvbHVtbiwgY2xhc3NfY29sdW1uLCBiaW5zLCBwb3NpdGl2ZV92YWx1ZSwgY29sb3I9J2ZvcmVzdGdyZWVuJywgbnVkZ2VfeD0wLCBudWRnZV95PTAuMDUpew0KICAjIEFzaWduYXIgbG9zIGdydXBvcyBhIGxhcyBvYnNlcnZhY2lvbmVzIGRlIGFjdWVyZG8gYSBsYSBwcm9iYWJpbGlkYWQgcHJlZGljaGENCiAgZGF0YXNldFsnZ3JvdXAnXSA8LSBiaW4oZGF0YXNldFtwcmVkaWN0ZWRfY29sdW1uXSwgbmJpbnMgPSBiaW5zLCBtZXRob2QgPSAnbCcsIGxhYmVscz1jKDE6YmlucykpDQogICMgQ29udGFyIGxhIGNhbnRpZGFkIGRlIGNhc29zIHBvc2l0aXZvcyBwb3IgZ3J1cG8NCiAgcG9zaXRpdmVfY2xhc3MgPC0gZGF0YXNldCAlPiUgZmlsdGVyKCEhc3ltKGNsYXNzX2NvbHVtbik9PXBvc2l0aXZlX3ZhbHVlKSAlPiUgZ3JvdXBfYnkoZ3JvdXApICU+JSBjb3VudCgpDQogICMgT2J0ZW5lciBsYSBtZWRpYSBkZSBsYXMgcHJlZGljY2lvbmVzIHBvciBncnVwbw0KICBITF9kZiA8LSBkYXRhc2V0ICU+JSBncm91cF9ieShncm91cCkgJT4lIHN1bW1hcmlzZShwcmVkPW1lYW4oISFzeW0ocHJlZGljdGVkX2NvbHVtbikpLCBjb3VudD1uKCkpICU+JQ0KICAgICAgICAgICAgaW5uZXJfam9pbiguLHBvc2l0aXZlX2NsYXNzKSAlPiUNCiAgICAgICAgICAgIG11dGF0ZShmcmVxPW4vY291bnQpDQogICMgR3LDoWZpY28gDQogIEhNX3Bsb3QgPC0gZ2dwbG90KEhMX2RmLCBhZXMoeD1wcmVkLCB5PWZyZXEpKSArIA0KICAgIGdlb21fcG9pbnQoYWVzKHNpemU9biksIGNvbG9yPWNvbG9yKSArDQogICAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uKSxudWRnZV95ID0gbnVkZ2VfeSkrDQogICAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZT0nZGFzaGVkJykgKyANCiAgICB0aGVtZV9idygpICsNCiAgICBsYWJzKHRpdGxlPSdIb3NtZXItTGVtZXNob3cnLCBzaXplPSdDYXNvcycsIHg9IlByb2JhYmlsaWRhZCBQcmVkaWNoYSIsIHk9IkZyZWN1ZW5jaWEgb2JzZXJ2YWRhIikNCiAgcmV0dXJuKEhNX3Bsb3QpDQp9DQpgYGANCg0KR2VuZXJhbW9zIGxvcyBncsOhZmljb3MgcGFzYW5kb2xlIGxvIHBhcsOhbWV0cm9zLiANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIG1vZGVsbyBjb21wbGV0bw0KSG9zbWVyX0xlbWVzaG93X3Bsb3QocHJlZGljdGlvbl9mdWxsLCAnLmZpdHRlZCcsICdTdXJ2aXZlZCcsIDEwLCAxKSArDQogIGxhYnMoc3VidGl0bGU9Ik1vZGVsbyBjb21wbGV0byIpDQojIG1vZGVsbyBtYWxvDQpIb3NtZXJfTGVtZXNob3dfcGxvdChwcmVkaWN0aW9uX2JhZCwgJy5maXR0ZWQnLCAnU3Vydml2ZWQnLCAxMCwgMSwgY29sb3IgPSAiZmlyZWJyaWNrIikgKyBsYWJzKHN1YnRpdGxlPSJNb2RlbG8gbWFsbyIpDQoNCmBgYA0KDQpFbiBsb3MgKipncsOhZmljb3MgZGUgSG9zbWVyLUxlbWVzaG93Kiogb2JzZXJ2YW1vczoNCg0KICAqIEVuIGVsIGVqZSBkZSBhYnNjaXNhcyBsYSBwcm9iYWJpbGlkYWQgcHJlZGljaGEgZGUgc3VwZXJ2aXZlbmNpYS4NCiAgDQogICogRW4gZWwgZWplIGRlIG9yZGVuYWRhcyBsYSBmcmVjdWVuY2lhIGRlIGNsYXNlLCBlbCBjb2NpZW50ZSBlbnRyZSBjYW50aWRhZCBkZSBpbmRpdmlkdW9zIFN1cnZpdmVkIHkgZWwgdG90YWwgZGUgaW5kaXZpZHVvcy4NCiAgDQogICogTGEgbMOtbmVhIHB1bnRlYWRhIGRlc2lnbmEgbGEgaWd1YWxkYWQgZW50cmUgcHJvYmFiaWxpZGFkIHByZWRpY2hhIHkgZnJlY3VlbmNpYSBkZSBjbGFzZS4NCiAgDQogICogTG9zIGPDrXJjdWxvcywgcXVlIHNlIGNvbnN0cnV5ZW4gZGUgbGEgc2lndWllbnRlIG1hbmVyYToNCiAgICAgICogU2UgZGl2aWRlbiBhIGxhcyBvYnNlcnZhY2lvbmVzIGVuIGJpbnMgZW4gYmFzZSBhIGxhIHByb2JhYmlsaWRhZCBwcmVkaWNoYQ0KICAgICAgKiBTZSBjYWxjdWxhIGxhIGZyZWN1ZW5jaWEgZGUgY2xhc2UgcGFyYSBjYWRhIGJpbg0KICAgICAgKiBFbiBiYXNlIGEgZXN0YXMgZG9zIGNvb3JkZW5hZGFzIHNlIHViaWNhIGFsIGPDrXJjdWxvIGVuIGVsIGdyw6FmaWNvDQogICAgICAqIEVsIG7Dum1lcm8geSB0YW1hw7FvIGluZGljYW4gbGEgY2FudGlkYWQgZGUgb2JzZXJ2YWNpb25lcyBlbiBkaWNobyBncnVwbw0KDQpBcXVlbGxvcyAqKmPDrXJjdWxvcyBxdWUgc2UgdWJpcXVlbiBwb3IgZW5jaW1hKiogZGUgbGEgbMOtbmVhIHB1bnRlYWRhIGluZGljYW4gcXVlIGVsICoqbW9kZWxvIGVzdMOhIHN1YmVzdGltYW5kbyoqIGxhIHByb2JhYmlsaWRhZCBwYXJhIGRpY2hvcyBncnVwb3MuIE1pZW50cmFzIHF1ZSBzaSBsb3MgKipjw61yY3Vsb3Mgc2UgdWJpY2FuIHBvciBkZWJham8qKiBlbCBtb2RlbG8gZXN0w6EgKipzb2JyZWVzdGltYW5kbyoqIGxhIHByb2JhYmlsaWRhZCBwYXJhIGRpY2hvcyBncnVwb3MuDQoNCsK/UGFyYSBxdcOpIHZhbG9yZXMgcGFyZWNlIGV4aXN0aXIgdW5hIHNvYnJlZXN0aW1hY2nDs24gZGUgbGEgcHJvYmFiaWxpZGFkPyDCv1BhcmEgY3XDoWxlcyBzdWJlc3RpbWFjacOzbj8NCg0KIyMjIyBDdXJ2YXMgUk9DDQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0V9DQojIENhbGN1bGFtb3MgY3VydmFzIFJPQw0Kcm9jX2Z1bGwgPC0gcm9jKHJlc3BvbnNlPXByZWRpY3Rpb25fZnVsbCRTdXJ2aXZlZCwgcHJlZGljdG9yPXByZWRpY3Rpb25fZnVsbCQuZml0dGVkKQ0Kcm9jX2JhZCA8LSByb2MocmVzcG9uc2U9cHJlZGljdGlvbl9iYWQkU3Vydml2ZWQsIHByZWRpY3Rvcj1wcmVkaWN0aW9uX2JhZCQuZml0dGVkKQ0KYGBgDQoNCkdyYWZpY2Ftb3MgYW1iYXMgZW4gdW4gbWlzbW8gcGxvdC4NCg0KYGBge3J9DQpnZ3JvYyhsaXN0KGZ1bGw9cm9jX2Z1bGwsIGJhZD1yb2NfYmFkKSwgc2l6ZT0xKSArIA0KICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDEsIGxpbmV0eXBlPSdkYXNoZWQnKSArDQogIHRoZW1lX2J3KCkgKyANCiAgbGFicyh0aXRsZT0nQ3VydmFzIFJPQycsIGNvbG9yPSdNb2RlbG8nKQ0KcHJpbnQocGFzdGUoJ0FVQzogTW9kZWxvIGNvbXBsZXRvJywgcm91bmQocm9jX2Z1bGwkYXVjLDMpKSkNCnByaW50KHBhc3RlKCdBVUM6IE1vZGVsbyBtYWxvJywgcm91bmQocm9jX2JhZCRhdWMsMykpKQ0KDQpgYGANCg0Kwr9RdcOpIHNpZ25pZmljYSBjYWRhIHVubyBkZSBsb3MgZWplcz8NCg0KIyMjIFB1bnRvIGRlIGNvcnRlDQoNCkhhc3RhIGFob3JhIGhlbW9zIGV2YWx1YWRvIGVsIG1vZGVsbyBkZSBtYW5lcmEgZ2VuZXJhbCwgcGVybyBlbCByZXN1bHRhZG8gZmluYWwgZGVsIG1vZGVsbyBkZWJlIGNvbnNpc3RpciBlbiBhc2lnbmFyIGEgbGEgcGVyc29uYSB1bmEgY2xhc2UgcHJlZGljaGEuIEVuIG51ZXN0cm8gY2FzbyBkZWJlbW9zIGVzdGFibGVjZXIgdW4gcHVudG8gZGUgY29ydGUgc2Vnw7puIGVsIGN1YWwgdmFtb3MgYSBzZXBhcmFyIGEgbGFzIHBlcnNvbmFzIGVuIHF1aWVuZXMgc29icmV2aXZlbiB5IHF1aWVuZXMgbm8uDQoNClByb2JhbW9zIHZhcmlvcyBwdW50b3MgZGUgY29ydGUgeSBncmFmaWNhbW9zIGVsIGFjY3VyYWN5LCBsYSBzZW5zaWJpbGlkYWQgbyByZWNhbGwsIGxhIGVzcGVjaWZpY2lkYWQgeSBsYSBwcmVjaXNpw7NuIHBhcmEgY2FkYSB1bm8gZGUgZWxsb3MuDQoNCnwgQ2xhc2VzIHByZWRpY2hhcyAvIENsYXNlcyB8IE5lZ2F0aXZhIHwgUG9zaXRpdmEgfA0KfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLXwtLS0tLS0tLS0tfA0KfCBOZWdhdGl2YSAgICAgICAgICAgICAgICAgfCBUcnVlIE5lZyB8IEZhbHNlIE5lZyB8DQp8IFBvc2l0aXZhICAgICAgICAgICAgICAgICB8IEZhbHNlIFBvcyB8IFRydWUgUG9zIHwNCg0KUmVjb3JkZW1vcyBxdWU6DQoNCiRhY2N1cmFjeSA9IFxmcmFje1RQK1ROfXtUUCtGUCtGTitUTn0kDQoNCiRzZW5zaXRpdml0eSA9IHJlY2FsbCA9IFxmcmFje1RQfXtUUCtGTn0kDQoNCiRzcGVjaWZpY2l0eSA9IFxmcmFje1ROfXtUTitGUH0kDQoNCiRwcmVjaXNpb24gPSBcZnJhY3tUUH17VFArRlB9JA0KDQpgYGB7cn0NCnByZWRpY3Rpb25fbWV0cmljcyA8LSBmdW5jdGlvbihjdXRvZmYsIHByZWRpY3Rpb25zPXByZWRpY3Rpb25fZnVsbCl7DQogIHRhYiA8LSBwcmVkaWN0aW9ucyAlPiUgDQogICAgbXV0YXRlKHByZWRpY3RlZF9jbGFzcyA9IGlmX2Vsc2UoLmZpdHRlZCA+IGN1dG9mZiwgMSwgMCksDQogICAgICAgICAgIFN1cnZpdmVkID0gZmFjdG9yKFN1cnZpdmVkKSkNCiAgY29uZnVzaW9uTWF0cml4KHRhYmxlKHRhYiRwcmVkaWN0ZWRfY2xhc3MsIHRhYiRTdXJ2aXZlZCksIHBvc2l0aXZlID0gIjEiKSAlPiUNCiAgICB0aWR5KCkgJT4lDQogICAgc2VsZWN0KHRlcm0sIGVzdGltYXRlKSAlPiUNCiAgICBmaWx0ZXIodGVybSAlaW4lIGMoJ2FjY3VyYWN5JywgJ3NlbnNpdGl2aXR5JywgJ3NwZWNpZmljaXR5JywgJ3ByZWNpc2lvbicpKSAlPiUNCiAgICBtdXRhdGUoY3V0b2ZmID0gY3V0b2ZmKQ0KfQ0KY3V0b2ZmcyA9IHNlcSgwLjA1LDAuOTUsMC4wMSkNCmxvZ2l0X3ByZWQgPSBtYXBfZGYoY3V0b2ZmcywgcHJlZGljdGlvbl9tZXRyaWNzKSAlPiUgDQogIG11dGF0ZSh0ZXJtID0gYXMuZmFjdG9yKHRlcm0pLCBlc3RpbWF0ZSA9IHJvdW5kKGVzdGltYXRlLCAzKSkNCmdncGxvdChsb2dpdF9wcmVkLCBhZXMoY3V0b2ZmLGVzdGltYXRlLCBncm91cD10ZXJtLCBjb2xvcj10ZXJtKSkgKyBnZW9tX2xpbmUoc2l6ZT0xKSArDQogIHRoZW1lX2J3KCkgKw0KICBsYWJzKHRpdGxlPSAnQWNjdXJhY3ksIFNlbnNpdGl2aXR5LCBTcGVjaWZpY2l0eSB5IFByZWNpc2lvbicsIHN1YnRpdGxlPSAnTW9kZWxvIGNvbXBsZXRvJywgY29sb3I9IiIpDQpgYGANCg0Kwr9RdcOpIHBvZGVtb3Mgb2JzZXJ2YXIgZW4gZWwgZ3LDoWZpY28/DQoNCsK/UG9kZW1vcyBkZWZpbmlyIHVuIGJ1ZW4gcHVudG8gZGUgY29ydGU/IMK/Q3XDoWwgc2Vyw61hPw0KDQrCv1BvciBxdcOpIGxhIGVzcGVjaWZpY2lkYWQgdGllbmUgZXNlIGNvbXBvcnRhbWllbnRvPw0KDQojIyMgRGF0YXNldCBkZSB0ZXN0aW5nDQoNClNlbGVjY2lvbmFtb3MgZWwgbW9kZWxvIGNvbXBsZXRvLCB5YSBxdWUgZXMgZWwgcXVlIG1heGltaXphYmEgZWwgcG9yY2VudGFqZSBkZSBkZXZpYW5jZSBleHBsaWNhZGEgeSBlbiBiYXNlIGEgbG8gcXVlIHZpbW9zIGRlZmluaW1vcyB1biBwdW50byBkZSBjb3J0ZSBlbiAwLjQgKHB1ZWRlbiBwcm9iYXIgb3Ryb3MpLCBkb25kZSBzZSBjcnV6YW4gc2Vuc2l0aXZpdHkgeSBzcGVjaWZpY2l0eS4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRX0NCnNlbF9jdXRvZmYgPSAwLjQNCiMgQ3JlYW1vcyBlbCBtb2RlbG8NCmZ1bGxfbW9kZWwgPC0gZ2xtKGxvZ2l0X2Zvcm11bGFzJFBjU0FGLCBmYW1pbHkgPSAnYmlub21pYWwnLCBkYXRhID0gZGZ0aXRhbmljX3RyYWluKQ0KIyBBZ3JlZ2Ftb3MgbGEgcHJlZGljY2lvbmVzIGFsIGRhdGFzZXQgZGUgdGVzdGVvDQp0YWJsZT0gYXVnbWVudCh4ID0gZnVsbF9tb2RlbCwgbmV3ZGF0YT1kZnRpdGFuaWNfdGVzdCwgdHlwZS5wcmVkaWN0PSdyZXNwb25zZScpIA0KIyBDbGFzaWZpY2Ftb3MgdXRpbGl6YW1vcyBlbCBwdW50byBkZSBjb3J0ZQ0KdGFibGU9dGFibGUgJT4lIA0KICBtdXRhdGUocHJlZGljdGVkX2NsYXNzID0gaWZfZWxzZSguZml0dGVkPnNlbF9jdXRvZmYsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwgDQogICAgICAgICBTdXJ2aXZlZCA9IGZhY3RvcihTdXJ2aXZlZCkpDQojIENyZWFtb3MgbGEgbWF0cml6IGRlIGNvbmZ1c2nDs24NCmNvbmZ1c2lvbk1hdHJpeCh0YWJsZSh0YWJsZSRwcmVkaWN0ZWRfY2xhc3MsIHRhYmxlJFN1cnZpdmVkKSwgcG9zaXRpdmUgPSAiMSIpDQpgYGANCkFsIGV4aXN0aXIgZGVzYmFsYW5jZW8gZGUgY2xhc2VzLCBlcyBwb3NpYmxlIHF1ZSBlbCBhbmFsaXN0YSBkZXNlZSBzYWJlciBzaSBsYSBwcmVjaXNpw7NuIGdlbmVyYWwgKEFjY3VyYWN5KSBkZSBzdSBtb2RlbG8gZXMgbWVqb3IgcXVlIGxhIHByb3BvcmNpw7NuIGRlIGRhdG9zIGNvbiBsYSBjbGFzZSBtYXlvcml0YXJpYSAoTm8taW5mb3JtYXRpb24gUmF0ZSkuIGNvbmZ1c2lvbk1hdHJpeCB1c2EgbGEgZnVuY2nDs24gYmlub20udGVzdCBwYXJhIHByb2JhciBxdWUgbGEgcHJlY2lzacOzbiAoQWNjKSBlcyBtZWpvciBxdWUgbGEgdGFzYSBzaW4gaW5mb3JtYWNpw7NuIChOSVIpLiBTaSBlbCBQLVZhbHVlIFtBY2MgPiBOSVJdIHJlc3VsdGEgc2lnbmlmaWNhdGl2bywgZW50b25jZXMgcG9kZW1vcyBkZWNpciBxdWUgZXhpc3RlIGV2aWRlbmNpYSBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhIHBhcmEgZGVjaXIgcXVlIGxhIHByZWNpc2nDs24gZGVsIG1vZGVsbyBlcyBtZWpvciBxdWUgbGEgdGFzYSBzaW4gaW5mb3JtYWNpw7NuLiANCg0KIyMgRGVzYmFsYW5jZW8gZGUgbGEgY2xhc2UNCg0KQWwgZXhwbG9yYXIgZWwgZGF0YXNldCB2aW1vcyBxdWUgZXhpc3TDrWEgY2llcnRvIGRlc2JhbGFuY2UgZGUgY2xhc2UuIEVzdG8gcHVlZGUgdGVuZXIgdW4gZWZlY3RvIGVuIGxhcyBlc3RpbWFjaW9uZXMgZGVsIG1vZGVsbyB5IHN1IGNsYXNpZmljYWNpw7NuIGZpbmFsLg0KDQpFeGlzdGVuIGRvcyBtYW5lcmFzIHNlbmNpbGxhcyBjb24gbGFzIGN1YWxlcyBwb2RlbW9zIHRyYWJhamFyIGNvbiB1bmEgY2xhc2UgZGVzYmFsYW5jZWFkYToNCg0KICAqIFNvYnJlLW11ZXN0cmVvIChvdmVyc2FtcGxpbmcpIGRlIGxhIGNsYXNlIG1pbm9yaXRhcmlhDQogIA0KICAqIFN1Yi1tdWVzdHJlbyAodW5kZXJzYW1wbGluZykgZGUgbGEgY2xhc2UgbWF5b3JpdGFyaWENCiAgDQpMYSBmdW5jacOzbiBgZ2xtYCBwdWVkZSB0b21hciBjb21vIGFyZ3VtZW50byB1bmEgY29sdW1uYSAoYHdlaWd0aHNgKSBkZSBwb25kZXJhZG9yZXMgcGFyYSBwb2RlciBoYWNlciBlc3RvLiBQb2RlbW9zIGFzaWduYXIgcGVzb3MgbWF5b3JlcyBhIDEgYSBsYSBjbGFzZSBtaW5vcml0YXJpYSAob3ZlcnNhbXBsaW5nKSBvIG1lbm9yZXMgYSAxIGEgbGEgY2xhc2UgbWF5b3JpdGFyaWEgKHVuZGVyc2FtcGxpbmcpLiBFbiBudWVzdHJvIHByb2JsZW1hIHZhbW9zIGEgcmVhbGl6YXIgdW4gc29icmVzYW1wbGVvIGRlIGxhIGNsYXNlIG1pbm9yaXRhcmlhLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMgQ3JlYW1vcyBsYSBjb2x1bW5hIGRlIHBvbmRlcmFkb3Jlcw0KdHJhaW4gPC0gZGZ0aXRhbmljX3RyYWluICU+JSBtdXRhdGUod3QgPSBpZl9lbHNlKFN1cnZpdmVkID09IDEsIDEuMSwgMSkpDQojIENyZWFtb3MgbG9zIG1vZGVsb3MgY29uIGxhIGRhdGEgJ2JhbGFuY2VhZGEnDQpiYWxhbmNlZF9tb2RlbHMgPC0gZGF0YV9mcmFtZShsb2dpdF9mb3JtdWxhcykgJT4lICMgZGF0YWZyYW1lIGEgcGFydGlyIGRlbCBvYmpldG8gZm9ybXVsYXMNCiAgbXV0YXRlKG1vZGVscyA9IG5hbWVzKGxvZ2l0X2Zvcm11bGFzKSwgIyBjb2x1bW5hIGNvbiBsb3Mgbm9tYnJlcyBkZSBsYXMgZm9ybXVsYXMNCiAgICAgICAgIGV4cHJlc3Npb24gPSBwYXN0ZShsb2dpdF9mb3JtdWxhcyksICMgY29sdW1uYSBjb24gbGFzIGV4cHJlc2lvbmVzIGRlIGxhcyBmb3JtdWxhcw0KICAgICAgICAgbW9kID0gbWFwKGxvZ2l0X2Zvcm11bGFzLCB+Z2xtKC4sIGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSB0cmFpbiwgd2VpZ2h0cyA9IHd0KSkpICNQYXNhbW9zIGxhIGNvbHVtbmEgd3QgY29tbyBwb25kZXJhZG9yZXMNCmBgYA0KDQpWZW1vcyBsYXMgZXN0aW1hY2lvbmVzIGRlIGxvcyBwYXLDoW1ldHJvcyBwYXJhIGVsIG1vZGVsbyBjb21wbGV0by4gwr9FeGlzdGVuIGNhbWJpb3M/DQoNCmBgYHtyLCAgd2FybmluZz1GQUxTRSwgZWNobz1GQUxTRX0NCmJhbGFuY2VkX21vZGVscyAlPiUgDQogIGZpbHRlcihtb2RlbHMgPT0gIlBjU0FGIikgJT4lDQogIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCx0aWR5KSkgJT4lDQogIHVubmVzdCh0aWR5LCAuZHJvcCA9IFRSVUUpICU+JSANCiAgbXV0YXRlKGVzdGltYXRlPXJvdW5kKGVzdGltYXRlLDUpLA0KICAgICAgICAgcC52YWx1ZT1yb3VuZChwLnZhbHVlLDQpKQ0KYGBgDQoNCkFob3JhIHZlYW1vcyBsYSBldmFsdWFjacOzbiBkZSBsb3MgbW9kZWxvcyDCv1F1w6kgcGFzw7MgY29uIGVsIHBvcmNlbnRhamUgZGUgZGV2aWFuY2UgZXhwbGljYWRhPyDCv1kgY29uIGxhIG51bGE/DQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KYmFsYW5jZWRfbW9kZWxzIDwtIGJhbGFuY2VkX21vZGVscyAlPiUgDQogIG11dGF0ZShnbGFuY2UgPSBtYXAobW9kLGdsYW5jZSkpDQpiYWxhbmNlZF9tb2RlbHMgJT4lIA0KICB1bm5lc3QoZ2xhbmNlLCAuZHJvcCA9IFRSVUUpICU+JQ0KICBtdXRhdGUocGVyY19leHBsYWluZWRfZGV2ID0gMS1kZXZpYW5jZS9udWxsLmRldmlhbmNlKSAlPiUgDQogIHNlbGVjdCgtYyhtb2RlbHMsIGRmLm51bGwsIEFJQywgQklDKSkgJT4lIA0KICBhcnJhbmdlKGRldmlhbmNlKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KYmFsYW5jZWRfbW9kZWxzIDwtIGJhbGFuY2VkX21vZGVscyAlPiUgDQogIG11dGF0ZShwcmVkPSBtYXAobW9kLGF1Z21lbnQsIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpKQ0KcHJlZGljdGlvbl9mdWxsIDwtIGJhbGFuY2VkX21vZGVscyAlPiUgDQogIGZpbHRlcihtb2RlbHM9PSJQY1NBRiIpICU+JSANCiAgdW5uZXN0KHByZWQsIC5kcm9wPVRSVUUpDQpyb2NfZnVsbCA8LSByb2MocmVzcG9uc2U9cHJlZGljdGlvbl9mdWxsJFN1cnZpdmVkLCBwcmVkaWN0b3I9cHJlZGljdGlvbl9mdWxsJC5maXR0ZWQpDQpwcmVkaWN0aW9uX2JhZCA8LSBiYWxhbmNlZF9tb2RlbHMgJT4lIA0KICBmaWx0ZXIobW9kZWxzPT0iYWdlIikgJT4lIA0KICB1bm5lc3QocHJlZCwgLmRyb3A9VFJVRSkNCnJvY19iYWQgPC0gcm9jKHJlc3BvbnNlPXByZWRpY3Rpb25fYmFkJFN1cnZpdmVkLCBwcmVkaWN0b3I9cHJlZGljdGlvbl9iYWQkLmZpdHRlZCkNCmBgYA0KDQojIyMgVmlvbGluIHBsb3RzLCBDdXJ2YXMgUk9DIHkgQVVDcw0KDQpSZWFsaXphbW9zIGxvcyBncsOhZmljb3MgZGUgdmlvbGluLCBsYXMgY3VydmFzIFJPQyB5IGNhbGN1bGFtb3MgbGFzIEFVQy4NCg0KYGBge3IsIHdhcm5pbmc9RiwgZWNobz1GQUxTRX0NCnZpb2xpbl9mdWxsID0gZ2dwbG90KHByZWRpY3Rpb25fZnVsbCwgYWVzKHggPSBTdXJ2aXZlZCwgeSA9LmZpdHRlZCwgZ3JvdXAgPSBTdXJ2aXZlZCwgZmlsbCA9IGZhY3RvcihTdXJ2aXZlZCkpKSArIA0KICBnZW9tX3Zpb2xpbigpICsNCiAgdGhlbWVfYncoKSArDQogIGd1aWRlcyhmaWxsPUZBTFNFKSArDQogIGxhYnModGl0bGU9J1Zpb2xpbiBwbG90Jywgc3VidGl0bGU9J01vZGVsbyBjb21wbGV0bycsIHk9J1ByZWRpY3RlZCBwcm9iYWJpbGl0eScpDQoNCnZpb2xpbl9iYWQ9Z2dwbG90KHByZWRpY3Rpb25fYmFkLCBhZXMoeCA9IFN1cnZpdmVkLCB5ID0uZml0dGVkLCBncm91cCA9IFN1cnZpdmVkLCBmaWxsID0gZmFjdG9yKFN1cnZpdmVkKSkpICsgDQogIGdlb21fdmlvbGluKCkgKyANCiAgdGhlbWVfYncoKSArDQogIGd1aWRlcyhmaWxsPUZBTFNFKSArDQogIGxhYnModGl0bGU9J1Zpb2xpbiBwbG90Jywgc3VidGl0bGU9J01vZGVsbyBtYWxvJywgeT0nUHJlZGljdGVkIHByb2JhYmlsaXR5JykNCg0KcGxvdF9ncmlkKHZpb2xpbl9iYWQsIHZpb2xpbl9mdWxsKQ0KDQpnZ3JvYyhsaXN0KGZ1bGw9cm9jX2Z1bGwsIGJhZD1yb2NfYmFkKSwgc2l6ZT0xKSArIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMSwgbGluZXR5cGU9J2Rhc2hlZCcpICsgdGhlbWVfYncoKSArIGxhYnModGl0bGU9J0N1cnZhcyBST0MnLCBjb2xvcj0nTW9kZWxvJykNCg0KcHJpbnQocGFzdGUoJ0FVQyBNb2RlbG8gY29tcGxldG86Jywgcm91bmQocm9jX2Z1bGwkYXVjLDMpKSkNCg0KcHJpbnQocGFzdGUoJ0FVQyBNb2RlbG8gbWFsbzonLCByb3VuZChyb2NfYmFkJGF1YywzKSkpDQoNCmBgYA0KDQo+IMK/RMOzbmRlIHNlIHZlbiBsb3MgY2FtYmlvcyBtw6FzIG5vdG9yaW9zIHJlc3BlY3RvIGEgbnVlc3Ryb3MgbW9kZWxvcyBhbnRlcmlvcmVzIHF1ZSBubyB0ZW7DrWFuIGVuIGN1ZW50YSBlbCBkZXNiYWxhbmNlIGRlIGxhIGNsYXNlPw0KDQojIyMgUHVudG8gZGUgY29ydGUNCg0KVm9sdmVtb3MgYSByZWFsaXphciBsYXMgcHJ1ZWJhcyBwYXJhIHZhcmlvcyBwdW50b3MgZGUgY29ydGUgeSBncmFmaWNhbW9zIGVsIGFjY3VyYWN5LCBsYSBzZW5zaWJpbGlkYWQsIGxhIGVzcGVjaWZpY2lkYWQsIGVsIHJlY2FsbCB5IGxhIHByZWNpc2lvbiBwYXJhIGNhZGEgdW5vIGRlIGVsbG9zLg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmN1dG9mZnMgPSBzZXEoMC4wNSwwLjk1LDAuMDEpDQpsb2dpdF9wcmVkPSBtYXBfZGZyKGN1dG9mZnMsIHByZWRpY3Rpb25fbWV0cmljcyklPiUgbXV0YXRlKHRlcm09YXMuZmFjdG9yKHRlcm0pKQ0KDQpnZ3Bsb3QobG9naXRfcHJlZCwgYWVzKGN1dG9mZixlc3RpbWF0ZSwgZ3JvdXA9dGVybSwgY29sb3I9dGVybSkpICsgZ2VvbV9saW5lKHNpemU9MSkgKw0KICB0aGVtZV9idygpICsNCiAgbGFicyh0aXRsZT0gJ0FjY3VyYWN5LCBQcmVjaXNpb24sIFNlbnNpdGl2aXR5IHkgU3BlY2lmaWNpdHknLCBzdWJ0aXRsZT0gJ01vZGVsbyBjb21wbGV0bycsIGNvbG9yPSIiKQ0KYGBgDQoNCsK/UXXDqSBjYW1iaW9zIHZlbW9zIHJlc3BlY3RvIGFsIGdyw6FmaWNvIGFudGVyaW9yPw0KDQojIyMgRGF0YXNldCBkZSB0ZXN0aW5nDQoNClByb2JhbW9zIGVuIGVsIGRhdGFzZXQgZGUgdGVzdGluZyBudWVzdHJvIG1vZGVsbyBiYWxhbmNlYWRvLiBObyBlcyBuZWNlc2FyaW8gcXVlIGxlIGNyZWVtb3MgcGVzb3MgYWwgZGF0YXNldCBkZSB0ZXN0ZW8uDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KZnVsbF9tb2RlbCA8LSBnbG0obG9naXRfZm9ybXVsYXMkUGNTQUYsIGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSB0cmFpbiwgd2VpZ2h0cyA9IHd0KQ0KDQp0YWJsZT0gYXVnbWVudCh4PWZ1bGxfbW9kZWwsIG5ld2RhdGE9ZGZ0aXRhbmljX3Rlc3QsIHR5cGUucHJlZGljdD0ncmVzcG9uc2UnKSANCg0KdGFibGU9dGFibGUgJT4lIG11dGF0ZShwcmVkaWN0ZWRfY2xhc3M9aWZfZWxzZSguZml0dGVkPjAuNDIsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwNCiAgICAgICAgICAgU3Vydml2ZWQgPSBmYWN0b3IoU3Vydml2ZWQpKQ0KDQpjb25mdXNpb25NYXRyaXgodGFibGUodGFibGUkcHJlZGljdGVkX2NsYXNzLCB0YWJsZSRTdXJ2aXZlZCksIHBvc2l0aXZlID0gIjEiKQ0KYGBgDQoNCg0K